객체 지향 프로그래밍(OOP)

객체 지향 프로그래밍 (Object Oriented Programming)은 객체를 중심으로 프로그램을 설계하고 구현하는 방법론이다. 자바스크립트에서 OOP는 클래스와 인스턴스, 생성자 함수, 상속 그리고 프로토타입 개념을 통해서 구현된다.

 

클래스와 인스턴스

클래스는 객체를 생성하기 위한 블루프린트 또는 템플릿이다. 클래스는 속성과 메서드를 정의하며, 인스턴스는 클래스를 기반으로 생성된 객체를 의미한다.

 

클래스

ES6 이전에는 자바스크립트에서 클래스를 정의하기 위해 생성자 함수와 프로토타입을 사용했다. 이후에는 'class' 키워드가 추가되어 키워드를 사용하여 클래스를 정의할 수 있게 되었다.

 

클래스 정의

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

// 인스턴스 생성
const bak = new Person('Bak', 30);
bak.greet(); // "Hello, my name is Bak and I am 30 years old."

 

생성자 함수

생성자 함수는 클래스를 정의하는 전통적인 방법으로 'new' 키워드를 사용하여 생성자 함수를 호출하면 새로운 인스턴스가 생성된다.

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// 인스턴스 생성
const bak = new Person('Bak', 25);
bak.greet(); // "Hello, my name is Bak and I am 25 years old."

 

상속과 프로토타입

상속은 한 클래스가 다른 클래스의 속성과 메서드를 상속받아 사용하는 개념이다. 자바스크립트에서는 프로토타입 기반 상속을 사용한다. 자바스크립트의 모든 객체는 프로토타입 객체를 가지고 있으며, 다른 객체로부터 메서드와 속성을 상속받을 수 있다.

 

클래스 상속

ES6에서는 'extends' 키워드를 사용하여 상속을 구현할 수 있다.

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 부모 클래스의 생성자를 호출
        this.breed = breed;
    }

    speak() {
        console.log(`${this.name} barks.`);
    }
}

const rex = new Dog('Rex', 'Labrador');
rex.speak(); // "Rex barks."

 

프로토타입 상속

ES6 이전에는 프로토타입을 사용하여 상속을 구현하였다.

function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function() {
    console.log(`${this.name} makes a noise.`);
};

function Dog(name, breed) {
    Animal.call(this, name); // 부모 생성자 호출
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
    console.log(`${this.name} barks.`);
};

const rex = new Dog('Rex', 'Labrador');
rex.speak(); // "Rex barks."

 

상속의 이점

상속의 특징을 제대로 이해하고 적절한 상황에 맞게 활용하는 것이 중요하다.

 

코드 재사용성

상속을 통해 기존 클래스의 기능을 재사용할  수 있기 때문에 이를 통해 중복 코드를 줄이고 새로운 기능을 쉽게 추가할 수 있다.

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    speak() {
        console.log(`${this.name} barks.`);
    }
}

const dog = new Dog('Rex');
dog.speak(); // "Rex barks."

 

'Dog' 클래스는 'Animal' 클래스의 'name' 속성과 'speak' 메서드를 재사용하여 기능을 확장한다.

 

코드의 가독성과 유지보수성 향상

상속으로 클래스 간의 관계를 명확히 하고, 코드를 더 구조적이고 일관성 있게 작성하여 코드의 가독성을 높이고 유지보수를 쉽게 할 수 있다.

class Shape {
    constructor(color) {
        this.color = color;
    }

    draw() {
        console.log('Drawing a shape');
    }
}

class Circle extends Shape {
    constructor(color, radius) {
        super(color);
        this.radius = radius;
    }

    draw() {
        super.draw();
        console.log(`Drawing a circle with radius ${this.radius}`);
    }
}

const circle = new Circle('red', 5);
circle.draw();
// "Drawing a shape"
// "Drawing a circle with radius 5"

 

'circle' 클래스는 'shape' 클래스를 상속받아 'color' 속성과 'draw' 메서드를 재사용하고 필요한 기능을 추가한다. 이를 통해서 코드가 더 구조적이고 명확해지도록 할 수 있다.

 

다형성(Polymorphism)

다형성은 같은 메서드가 다양한 클래스에서 다른 방식으로 동작하도록 하는 것으로 코드의 유연성을 높일 수 있다.

class Animal {
    speak() {
        console.log('Animal makes a noise');
    }
}

class Dog extends Animal {
    speak() {
        console.log('Dog barks');
    }
}

class Cat extends Animal {
    speak() {
        console.log('Cat meows');
    }
}

function makeAnimalSpeak(animal) {
    animal.speak();
}

const dog = new Dog();
const cat = new Cat();

makeAnimalSpeak(dog); // "Dog barks"
makeAnimalSpeak(cat); // "Cat meows"

 

'makeAnimalSpeak' 함수는 'Animal' 클래스를 상속받는 객체를 인수로 받아 다형성을 통해 다양한 동작을 수행할 수 있다.

 

클래스 간의 계층 구조 정의

상속 구조는 클래스 간의 계층 구조를 정의할 수 있어 복잡한 시스템을 더 쉽게 이해하고 관리할 수 있다.

class Vehicle {
    constructor(brand) {
        this.brand = brand;
    }

    start() {
        console.log('Starting the vehicle');
    }
}

class Car extends Vehicle {
    constructor(brand, model) {
        super(brand);
        this.model = model;
    }

    start() {
        super.start();
        console.log(`Starting the car ${this.brand} ${this.model}`);
    }
}

const car = new Car('Toyota', 'Corolla');
car.start();
// "Starting the vehicle"
// "Starting the car Toyota Corolla"

 

'vehicle' 클래스는 기본적인 차량의 속성과 메서드를 정의하고, 'Car' 클래스는 이를 상속받아 구체적인 차량 모델을 정의한다. 이는 클래스 간의 관계를 명확히 하고 계층 구조를 쉽게 이해할 수 있다.

 

객체 간의 관계 모델링

상속을 사용하면 객체 간의 관계를 모델링하여 복잡한 시스템을 설계하고 구현하는 데 유용하다.

class Employee {
    constructor(name, position) {
        this.name = name;
        this.position = position;
    }

    work() {
        console.log(`${this.name} is working as a ${this.position}`);
    }
}

class Manager extends Employee {
    constructor(name, department) {
        super(name, 'Manager');
        this.department = department;
    }

    manage() {
        console.log(`${this.name} is managing the ${this.department} department`);
    }
}

const manager = new Manager('Bak', 'Sales');
manager.work(); // "Bak is working as a Manager"
manager.manage(); // "Bak is managing the Sales department"

 

'manager' 클래스는 'Employee' 클래스를 상속받아 관리자의 속성과 메서드를 정의하여 객체 간의 관계를 명확히 모델링할 수 있다.

 

상속은 객체 지향 프로그래밍에서 매우 유용한 개념이지만 과도하게 또는 잘못된 방식으로 사용하게 되면 오히려 코드의 복잡성을 증가시켜 유지보수를 어렵게 만들 수 있다.

728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

this  (0) 2024.08.26
JavaScript #17 고차 함수(High-order Function)  (2) 2024.07.24
JavaScript #15 비동기 자바스크립트  (0) 2024.07.23
JavaScript #14 ES6+ 문법  (4) 2024.07.23
JavaScript #13 DOM(Document Object Model)  (0) 2024.07.23

Collections

. Net Framework에서 사용되던 라이브러리이다. Generic 기능이 도입되기 이전에 사용되었으며 Collections의 클래스들은 모든 요소를 object 타입으로 처리하여  요소를 다룰 때에 형변환을 필요로 한다.

 

대표적의로 ArrayList, Hashtable, sortedList, Stack, Queue 등이 있다.

 

Generic

Collections의 클래스들은 형변환이 필요하기 때문에 잘못된 타입을 사용할 경우 에러가 발생하게 된다. 

이 문제를 해결하기 위해서 안정성을 제공하는 새로운 클래스들이 Generic 네임스페이스로 추가되었다.

 

대표적으로 List, Dictionary, Queue <T>, Stack <T> 등이 있다.

 

 

Collections Generic
ArrayList List<T>
Hashtable Dictionary<TKey, TValue>
Queue Queue<T>
Stack Stack<T>

 

Compare

옛날부터 작성된 코드인 경우에는 Collections 라이브러리를 사용하기 위해서 해당 네임스페이스를 선언해 주는 경우가 많다. 거기다 새로 추가된 Generic도 사용하기 위해서 두 라이브러리를 모두 선언하는 경우가 많은데 공식 문서에 따르면 Collections에서 사용하는 클래스들은 Generic에 안정성이 추가된 대체할 수 있는 클래스들이 있기 때문에 되도록이면 Generic만 사용하기를 권장한다.

 

대표적으로 ArrayList와 List<T>를 비교해 본다. 

using System;
using System.Collections;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // ArrayList
        ArrayList arrayList = new ArrayList();

        // 요소 추가
        arrayList.Add("Hello");
        arrayList.Add(30);
        arrayList.Add(true);

        // 요소 접근
        string str = (string)arrayList[0];
        int num = (int)arrayList[1];
        bool flag = (bool)arrayList[2];

        Console.WriteLine(str);  // 출력: Hello
        Console.WriteLine(num);  // 출력: 30
        Console.WriteLine(flag); // 출력: True
        
        // List<T>
        List<object> list = new List<object>();

        // 요소 추가
        list.Add("Hello");
        list.Add(30);
        list.Add(true);

        // 요소 접근
        string str = list[0];
        int num = list[1];
        bool flag = list[2];

        Console.WriteLine(str);  // 출력: Hello
        Console.WriteLine(num);  // 출력: 30
        Console.WriteLine(flag); // 출력: True
    }
}

arrayList와 list 변수 모두 가변 배열로 요소를 추가하고 인덱스로 접근하여 값을 사용하고 있다.

하지만 사용법에서 차이가 발생한다.

 

ArrayList는 Add 메서드를 사용하여 요소를 추가하고 접근할 때에는 형변환을 수행해야 한다. 모든 요소를 object 타입으로 처리하므로 요소를 사용하기 전에 타입을 캐스팅해야 한다.

 

하지만 List 요소의 타입은 컴파일 시점에 검사 되기 때문에 추가한 요소에 접근할 때 형변환이 따로 필요하지 않다. 

즉 Generic을 사용하게 되면 타입의 안정성이 보장되고 컴파일러가 타입을 검사를 수행하기 때문에 타입으로 인해 발생하는 문제를 사전에 발견할 수 있으며 형변환 코드를 작성할 필요가 없기 때문에 코드가 간결해진다.

728x90
반응형

C#은 객체지향 언어로 객체지향 프로그래밍을 위한 개념들이 있다.

 

Class

객체를 정의하는 템플릿이며 객체의 상태를 나타내는 필드와 동작을 나타내는 메서드를 포함한다.

즉 클래스는 객체를 표현하기 위한 설계도로 볼 수 있다.

public class MyClass
{
    // field, 멤버 변수
    public int id;
    public string name;
    public int age;
    
    // method, 멤버 함수
    public void Introduc()
    {
        string introduce = "My age : " + agem + "\nMy name : " + name;
    	Cosole.WriteLine(introduce);
    }
}

MyClass라는 객체에는 id, name, age 멤버변수와 Introduce 메서드를 포함하고 있다.

 

Instance

클래스의 인스턴스로 실제로 메모리에 할당된 데이터이다. 객체는 클래스에서 정의된 필드와 메서드를 사용할 수 있으며 개별적인 상태를 유지한다.

public void Main(string[] args)
{
    // Instance, MyClass 클래스의 객체
    MyClass myClass = new MyClass();
    myClass.id = 1;
    myClass.name = "bak";
    myClass.age = 30;
    myClass.Introduce();
    
    // Instance, myClass와는 개별적인 객체
    MyClass myClass2 = new MyClass();
    myClass2.id = 2;
    myClass2.name = "kim";
    myClass2.age = 25;
    myClass2.Introduce();
}

동일한 MyClass를 인스턴스화한 myClass와 myClass2는 개별적인 객체이다.

 

Inherit

클래스 간의 계층 구조를 형성하여 코드의 재사용성과 구조화를 실현한다. 기존 클래스를 기반으로 새로운 클래스를 정의하고 확장할 수 있는데 ㅅ상속을 통해 부모 클래스의 멤버를 자식 클래스에서 사용하는 것이 가능하다.

 

public class Parent
{ 
    public int id;
    public string name;
    
    public void MethodParent()
    {
    	Console.Log(id, name);
    }
}

public class Child
{
    public void MethodChild()
    {
    	id = 10;
        name = "bak";
        MethodParent();
    }
}

재사용할 코드는 부모 클래스로 만들어서 자식 클래스에서 상속하여 그대로 사용할 수 있다.

 

Polymophism

다형성은 같은 이름의 메서드나 속성을 다른 방식으로 구현하는 개념이다. 다형성은 상속과 관련이 깊으며 부모 클래스의 타입으로 자식 클래스의 객체를 참조하면 다형성을 활용할 수 있다.

 

public class Parent
{
    public virtual void MethodParent()
    {
    	Console.WriteLine("Call Parent Method");
    }
}

public class Child
{
 	public override void MethodParent()
    {
    	Console.WriteLine("Call Parent Method From Child");
    }
}

다형성의 대표적인 예는 부모 클래스의 메서드를 자식에서 오버라이딩하는 경우가 있다.

 

Encapsulation

객체의 상태와 동작을 외부로부터 감추는것을 뜻한다.  클래스는 필드와 메서드를 적절한 접근 제한자로 제어하여 캡슐화를 구현할 수 있다. 이는 객체의 내부 구현을 보호하며 외부에서는 필요한 기능만 사용할 수 있도록 한다.

 

public class MyClass
{
    private string name;
    private int age;
    
    public void SetName(string name)
    {
    	this.name = name;
    }
    
    public string GetName()
    {
    	return name;
    }
    
    public void SetAge(int age)
    {
    	this.age = age;
    }
    
    public int GetAge()
    {
    	return age;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
    	MyClass myClass = new MyClass();
        myClass.SetName("Bak");
        myClass.SetAge(30);
        Console.WriteLine("Name : " + myClass.GetName());
        Console.WriteLine("Age : " + myClass.GetAge());
    }
}

클래스에서 접근을 허용할 부분을 public 불필요한 부분은 private로 접근 제한자를 두어 필요한 부분만 제어할 수 있도록 한다.

 

Abstract

복잡한 시스템을 단순화하고 핵심적인 요소만을 추출하여 모델링하는 과정을 말한다. 추상화는 클래스의 공통적인 특징을 추출하여 부모 클래스나 인터페이스로 정의할 수 있다.

 

public class Person
{
    public int age;
    public string name;
    public int countryCode;
    
    public virtual void Eat() {}
    public virtual void Sleep() {}
    public virtual void Work() {}
}

public class Student : Person
{
    public override void Work()
    {
    	Console.WirteLine("Studying");
    }
}

public class Teacher : Person
{
    public override void Work()
    {
    	Console.WriteLine("Teaching");
    }
}

추상화는 클래스를 만들 때 필요한 기본 개념으로 재사용, 다형성 등을 고려하여 최대한 구체적이지 않으면서 공통적인 부분을 부모 클래스로 만들 때 필요하다.

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 인스턴스 생성 키워드 : new - 생성자와 소멸자  (0) 2024.05.06
C# 라이브러리 Collections, Generic  (0) 2023.05.16
C# Operator 키워드  (0) 2023.04.27
C# Method Parameter 키워드  (0) 2023.04.27
C# Namespace 키워드  (0) 2023.04.27

Visual Studio 2022

 

class 생성

멤버변수 int 하나

public class Test
{
	int data_1;
}

public static void Main(string[] args)
{
	Test test = new Test();
}

C# Class Size Test (Field int 1)

int 하나인데 크기가 24 바이트이다.

int = 4byte 인데 왜 6배나 되는걸까?

=>6개 이상일 때 크기 확인 해보기로함

public class Test
{
	public int data_1;
	public int data_2;
	public int data_3;
	public int data_4;
	public int data_5;
	public int data_6;
}

일단 int 6개로 테스트, 기대 결과는 그대로 24byte

C# Class Size Test (Field int 6)

이게 무슨일이지, 40 byte 가 나옴

처음 결과를 기준으로 생각해보면 24 + 16 즉 16 byre가 추가됨.

4(int) * 4(count) 라고 생각해보고 다시 필드를 int * 3으로 테스트

 

public class Test
{
    public int data_1;
    public int data_2;
    //public int data_3;
    //public int data_4;
    //public int data_5;
    //public int data_6;
}

예상한대로면 처음 결과인 24가 나와야함

C# Class Size Test (Field int 2)

결과는 기대한대로 나왔지만 여전히 24라는 크기가 이해가 안감,

혹시나해서 클래스를 인스턴스화 안했을때 한번 확인해봤지만 당연하게 해당 메모리 안잡힘

 

이번엔 하나씩 필드 늘려가면서 확인해보기로함

C# Class Size Test (Field int 3)

int * 4 일때는 동일하게 32

C# Class Size Test (Field int 5)

int * 5 부터 40 으로 잡혔음

 

그럼 그냥 빈 클래스는 얼마로 잡히나 확인했는데

애초에 비어있는 클래스를 인스턴스하면 24 byte 할당됨

여기서 2개씩 int 추가될 때마다 8 byte가 늘어나는데 이 늘어나는 값은 4(int) * 2(count) 맞음

 

비어있는 클래스가 왜 24 byte의 힙을 차지하는지 파악해보면될듯함

일단 Program 외부에 Test_2 클래스, 내부에는 Test_1 선언 후 비교

public class Test_2 { }
public class Program
{
    public class Test_1 { }
    public static void Main(string[] args)
    {
        Test_1 test_1 = new Test_1();
        Test_2 test_2 = new Test_2();
    }
}

C# Class Size Test_1 / Test_2 (empty)

테스트 해본 결과 클래스를 할당하면 기본 크기가 24로 잡히고 이 안에 8byte 까지는 포함시킬 수 있는걸로 보인다.

즉 기본 24 = 16(사용됨) + 8(여유 공간) 이고 크기가 24 byte를 넘을 경우 8byte 단위로 확장되는걸로 파악됨

 

구글링 해본 결과 내가 원하는 정보를 찾을 수 있었다.

 

OF MEMORY AND STRING_Jon Sket

 

본래 글의 취지는 .Net에서 ASCII 문자열의 효율적인 표현에 관한것에 대한 답이다.

위 글에서는 x86과 x64를 모두 비교하고 있는데 이 글에서는 x64를 기준으로 정리한다.

 

그럼 이제 왜 0byte는 될수없는지 그리고 24byte 중 사용할 수 없는 16byte와 사용 가능한 8byte는 무엇 때문인지 정리한다.

 

우선 C#에서 모든 타입은 object 타입을 베이스로한다. 그리고 이 object 타입의 크기는 24byte이다.

이 24byte의 크기는 16byte의 오버헤드와 참조를 위한 8byte로 이루어진다. 

 

오버헤드란 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 말한다. 즉 C#의 object를 메모리에 유지시키는데 필요한 메모리 양이 16byte이며 .Net의 CLR은 데이터가 없는 object에서 작업할 수 없기 때문에 오버헤드만 가지고는 object를 사용할 수 없고 여유 공간이 필요하며 이 크기가 8byte이다. 따라서 선언될 수 있는 가장 작은 크기의 메모리는 24byte가 된다.

 

이 중 여유공간인 8byte는 실제 데이터를 저장할 수 있기 때문에 int가 2개 포함된 class역시 24byte였다. 이 8byte는 전체를 사용할 수 있는 공간이기 때문에 1byte로 8를 사용하는것도 가능하다. 그리고 int가 3개가 될때 24byte에서 8byte로 증가했는데 이것은 메모리의 경계인 패딩의 크기가 8byte였기 때문이다. 

 

class 역시 object 타입을 베이스로 하고 있고 빈 class를 인스턴스화 하는것은 object를 생성하는것과 마찬가지이기 때문에 빈 class가 24byte라는것도 이제 납득이 간다. 

 

오버헤드에 대한 내용

overhead - Andrew Hunter
overhead - Stack Overflow

 

왜 이렇게 많은 공간이 필요한지 또 이 공간은 어떤식으로 사용되는지 궁금한데 이 부분은 더 자세히 찾아봐야 될것같다.

 

정리

64bit 기준으로 

1. 비어있는 class 또한 하나의 object이다. 따라서 24byte 메모리를 차지한다.

2. 이 메모리는 16byte의 내부 데이터 참조형으로 사용되는 오버헤드와 8byte의 여유공간으로 구성된다.

3. 여유공간은 모두 사용이 가능하며 메모리는 8byte의 패딩을 가지고 있다.

 

728x90
반응형

'Memo' 카테고리의 다른 글

다국어를 제공하는 방법에 대해서  (0) 2023.05.10

+ Recent posts