객체 지향 프로그래밍(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

new

메모리 할당

new 키워드를 사용하면 CLR은 관리되는 힙에 객체를 위한 메모리를 할당한다. 

할당된 메모리는 해당 객체에 대한 모든 참조가 없어질 때까지 메모리에 존재하고 더 이상 참조되지 않을 때 GC에 의해 관리된다.

public class MyClass
{
    public int Number { get; set; }
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass();
        myObject.Number = 1;
        Console.WriteLine(myObject.Number);
    }
}

 

 

생성자

생성자는 객체가 할당될 때 호출되는 생명주기 메서드(Lifecycle Methods)이다.

클래스의 객체를 생성하는 방식을 생각해 보면 new 키워드 뒤에 클래스명의 함수를 쓰는데 이 함수는 클래스에서 따로 작성하지 않았는데도 문제없이 컴파일된다.

 

MyClass myObject = new MyClass();

 

그 이유는 클래스에 별도로 생성자에 대한 작성을 하지 않아도 컴파일 단계에서 자동으로 기본 생성자를 만들어서 사용하기 때문에 컴파일 에러가 발생하지 않게 된다.

 

기본 생성자의 형태는 클래스 명의 메서드로 함수 내부에는 비어있는 형태로 볼 수 있다.

 

public class MyClass
{
    public int Number { get; set; }
    // 기본 생성자 형태
    public Number(){}
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass();
        myObject.Number = 1;
        Console.WriteLine(myObject.Number);
    }
}


생성자의 접근제한자를 private 등을 사용해서 클래스의 인스턴스 생성을 제한할 수 있다.

 

생성자는 파라미터를 추가한 형태로 선언이 가능하며 클래스의 인스턴스가 생성될 때 필드나 프로퍼티를 초기화하는 방법으로 사용할 수 있다.

 

public class MyClass
{
    public int Number { get; set; }
    private string _id;
    private string _pw;
    private int _age;
    private bool _isAgree;
    
    // 기본 생성자 형태
    public Number(string id, string pw, int age, bool isAgree)
    {
    	_id = id;
        _pw = pw;
        _age = age;
        _isAgree = isAgree;
    }
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass("bak", "****", 19, true);
        // 에러 발생
        MyClass myObject = new MyClass();
    }
}

 

이렇게 하면 인스턴스를 생성과 동시에 필드값을 초기화할 수 있다.

주의할 점은 이렇게 파라미터를 받는 생성자를 작성하게 되면 컴파일러에서는 더 이상 기본 생성자는 만들어 주지 않기 때문에 기본 생성자를 사용하려고 하면 컴파일 에러가 발생하게 되므로 기본 생성자도 사용하기를 원하면 추가로 작성해야 한다. 즉 생성자도 오버로딩이 가능하기 때문에 다양한 매개변수를 받는 생성자의 선언이 가능하다.

 

소멸자

생성자와는 호출 시점에만 차이가 있는 생명주기 메서드이다. (파괴자라고도 한다.)

소멸자는 객체가 소멸되는 시점에 호출되며 C#에서는 더 이상 객체가 참조되는 곳이 없을 때 GC에 의해서 관리되어 소멸될 때 소멸자가 호출된다.

 

따라서 소멸자의 호출 시점이 명확하지 않으므로 소멸자 내부에서 어떠한 기능을 수행시키게 한다면 동작에 대한 기대가 힘들기 때문에 C#에서는 이를 활용하는 경우는 드물다. 

 

public class MyClass
{
    public Number(){}
    // 소멸자 선언
    ~Number(){}
}

 

소멸자는 직접 호출도 불가능하기 때문에 별도의 접근 제한자를 지정할 필요도 없다.

728x90
반응형

+ Recent posts