디자인 패턴을 배워보자 1일차 - 객체지향 4요소와 함수형 프로그래밍

0

Design pattern

목록 보기
1/3

그만... 하고싶어도 끝이 없는게 프로그래밍 공부아닐까 싶다. 이제는 전공자보다 공부잘해! 라고 하는 친구들을 보면 부럽다. 나도 저렇게 속편했으면 하지만 아쉽게도 그런 인간이 못되나보다.

얼마전에 회사에서 디자인 패턴 관련 교육이 있었는데, 갑자기 기억이 안나서 예전에 배운 디자인 패턴이나 다시 복습해보려고 한다.

한가지 명심해야할 것이, 디자인 패턴은 만능의 레시피가 아니다. '디자인 패턴을 적용한 코드는 모두 좋고, 적용 못한 코드는 별로다' 라는 말은 잘못되었다. 대체로 좋은 경우들이 많지만, 빠르게 개발하는 상황에서 유지 보수 신경쓴다고 두 시간 째 고민만 하고 있는 것보다 그냥 1초만에 코드를 복붙하는 경우가 좋을 때도 있다. 그런 의미에서 객체지향에 반기를 든 프로그래밍 이론들과 언어들도 꽤 많다. ex) golang

추가적으로 디자인 패턴은 성능이 매우 구리다. 그래서 성능에 민감한 코드를 만들 때는 디자인 패턴을 남발하는 것은 좋지 않다. 이건 그냥 쭉 코드를 적은 것이 객체 지향 코드보다 성능이 좋다는 것만 봐도 알 수 있다. 따라서, 유지 보수가 중요한 관점인 코드인 경우는 디자인 패턴으로 만드는 것이 확실히 좋지만, 몇 번만 쓸 코드나 속도나 타이밍이 중요한 코드는 디자인 패턴에 집착하지 않는 것이 좋다.

객체지향 프로그래밍의 4가지 특성과 함수형 프로그래밍

해당 포스트는 java 또는 c++과 같은 언어에 대한 기본적인 이해를 바탕으로 작성되었습니다.

1. 객체

객체는 객체지향 프로그래밍(OOP)의 주요 요소이다. 객체는 상태(state)와 동작(behavior)를 가진다.

어렵게 상태동작이라고 말하지만, 사실 상태맴버 변수를 말하고, 동작메서드를 말한다.

클래스를 템플릿으로 생각하면 객체는 템플릿(클래스)의 구현이다. 가령, 인간이 인간이 가질 수 있는 행동과 속성을 정의하는 클래스라면, 나 그리고 모든 사람들은
인간이되기 위한 모든 요구사항을 충족했기 때문에 인간 클래스의 대상이다. 자동차로 생각하면 현대, BMW 등의 자동차들이 되겠다.

객체 지향프로그램은 4가지 원칙을 기반으로 한다.

  1. Encapsulation ( 캡슐화 )
  2. Abstraction ( 추상화 - 추출)
  3. Inheritance ( 상속 )
  4. Polymorphism (subtyping polymorphism) ( 다형성 )

하나하나 공부해보자

1. Encapsulation ( 캡슐화 )

캡슐화는 기본적으로 속성(상태)동작(메서드)의 바인딩을 의미한다. 즉, 개체의 속성과 동작을
한 곳에서 유지 관리 및 확장이 쉽도록 한 것이다. 또한, 캡슐화는 사용자에게 불필요한 세부 정보를 숨기는 메커니즘을 제공한다.
java에서는 클래스 사용자에게 표시되는 항목과 숨겨진 항목을 관리하기 위해 메서드 및 속성에 대한 액세스 지정자(public, private, protected, default)를 제공할 수 있다.

정리하면 다음과 같다.

  1. 객체 속성과 행위를 하나로 묶는다.
  2. 실제 구현 내용의 일부를 외부에 감추어 은닉한다.

캡슐화는 객체지향언어의 가장 기본 원칙 중 하나이다. 클래스를 만드는 것 자체가 하나의 캡슐화인 것이다. 그렇기 때문에
캡슐화는 다른 모듈을 분리하는 데 도움이 된다. 분리된 모듈은 독립적으로 개발 및 유지 관리할 수 있다.

Data Hiding(information hiding) vs Encapsulation: 한 가지 오해하지 말아야할 것이 있는데, encapsulation은 정보를 보호하기위해서 사용한다고 말하면 안된다.
아마 면접에서 그렇게 말하면 좋은 점수받기는 쉽지 않다. 이유는 Data Hiding과의 차이점을 설명하라면 애매해지기 때문이다.
Data hiding은 클래스의 맴버인, 속성(맴버변수)과 행동(메서드)를 외부 사용자에게서 보호하기 위해 사용하는 개념으로, 접근 지시자(public, protected, private)가 대표적이다.
반면, Encapsulation역시도 Data hiding처럼 데이터를 보호한다는 개념이 있지만, 흩뿌려진 속성과 행동을 하나의 캡슐로 모아준다는 의미가 더 크다.
즉, 클래스를 만드는 것 자체가 흩뿌려진 속성과 행동을 바인딩시켜 하나의 틀로 만드는 것이므로 캡슐화라고 말할 수 있다. 여기에 더불어 지시자를 넣어 보안을 감안할 수 있다. 따라서, Data Hiding
Encapsulation의 한 부분이며, Encapsulation이 더 큰 개념이다.

참고로 분리된 모듈/클래스/코드가 외부 노출 동작에 영향을 주지 않고, 내부적으로 변경되는 기술을 코드 리팩토링이라고 한다.

2. Abstraction ( 추상화 , 추출 )

추상화캡슐화와 밀접한 관련이 있으며 어느 정도 중첩된다. 추상화개체가 하는 일을 노출시키고, 개체가 그 일을 하는 방법을 숨기는 매커니즘을 제공한다.

가장 대표적인 예가 바로, 자동차이다. 운전을 하기위해 자동차 내부가 무엇이며 어떻게 작동하는 지 알 필요가 없다. 그러나, 자동차를 구동시키고 조작하는 데이터와 행동은 알아야 한다.
데이터는 자동차의 대시보드가 되며, 행동은 우리가 자동차를 운전하는데 사용하는 컨트롤러가 된다. 다시 말하지만, 우리는 그 내부적으로 어떻게 작동하는 지 알필요가 없다.

즉, 추상화는 어떤 객체의 속성과 행동 중에서 사용자에게 보여질 수 있는 지 추출하는 것이다. 그러나, 사용자가 그 내부의 동작에 대해서는 관심이 없도록 한다.
오직 어떻게 사용하는 지가 중요하도록하지, 어떻게 동작한다는 모르게 한다.

Data hiding vs Abstraction: 정보 은닉추상화를 헷갈릴 수도 있는데, 정보 은닉은 말 그대로, 사용자에게 어떤 정보가 접근되지 말아야 하는 지를 결정하는 것이다. 즉, 보안을 위한 것이다.
반면, 추상화복잡성을 감추기 위한 것이다. 즉, 오로지 외부에 보여질 객체의 양상에 집중하는 것이다. 즉, 사용자가 보는 개념적인 경계에 집중하여 사용자가 원하는(관심있어하는) 정보나 중요하게 생각하는 부분만
제공하고, 관심이 없거나 알 필요없는 내용은 숨기는 것이다. 정리하자면, 정보 은닉은 데이터를 보호하기 위한 목적이다. 반면, 추상화는 사용자 원하는 속성, 행동을 노출시키고 관심없는 부분을 억눌러 사용자에게 단순함을 제공하고, 복잡성을 억누르는 것이다.
그래서, abstration method나 interface는 내부구현이 없고, 사용자들이 자유롭게 접근할 수 있도록 한다. 이를 통해 추상화를 구현하여 사용자들에게 복잡성을 내리고, 단순함을 제공하는 것이다.

3. Inheritance ( 상속 )

상속은 객체 또는 클래스를 다른 객체 또는 클래스에 기반하는 기능이다. 즉, 자식 클래스는 부모 클래스의 속성과 행동을 물려받고, 추가적인 속성과 동작을 추가할 수 있다.

가령, 모든 자동차들은 이름이라는 속성과 drive라는 행동을 갖는다면, Vehicle이라는 클래스가 이 속성과 행동을 갖고, 현대, BMW 등 자동차 제조사들은 이 속성과 행동을 갖도록 vehicle을 상속하는 것이다.

public class Vehicle {
    public String name;
    public Vehicle(String name){
        this.name = name;
    }
    public void drive(){
        System.out.println("my car" + name + " bong");
    }
}


public class BMW extends Vehicle{
    public BMW(String name){
        super(name);
    }
    public void BMWDrive(){
        drive();
        System.out.print("BMW!!");
    }
}

class Main
{
    public static void main (String[] args)
    {
        BMW bmw = new BMW("250d");
        bmw.BMWDrive();
    }
}

BMWVehicle을 상속하였기 때문에 Vehicle이 가진 속성과 행동을 가진다. 때문에 name과 drive를 사용할 수 있다. 또한, 추가적으로
vehicle에는 없는 BMWDrive를 사용할 수 있다. 이 처럼 객체는 상속을 통해 자신을 확장해나가고, 추가적인 상태와 동작을 구현해나갈 수 있다.

4. Polymorphism ( 다형성 )

넓은 의미에서 다형성은 다른 유형의 엔티티(객체)에 대해 동일한 인터페이스를 사용할 수 있는 옵션을 제공한다.
다형성에는 컴파일 시간런타임이라는 두 가지 주요 유형이 있다.

  1. 컴파일 타임 : 오버로딩이 대표적인 예이다. shape라는 클래스에 calc 매서드가 있는데, 매개변수를 하나만 주면 반지름으로 인식하여
    원의 넓이를 반환한다. 반면 매개변수가 2개이면 직사각형을 계산하도록 한다. 이는 컴파일 타임에 어떤 메서드가 진짜 호출되어야 하는 메서드인지 확인할 수 있다.
public class Shape {
    public double calc(int radius){
        return radius*radius*3.14;
    }
    public int calc(int height, int width){
        return height * width;
    }
}

바로 다음과 같은 경우를 말하는 것이다. 오버로딩과 같은 것은 컴파일 타임에 결정할 수 있으므로 큰 문제가 되지 않는다.

  1. 런타임 : 몇몇 분들은 런타임 다형성이 진짜 다형성이라고 한다. 이는 부모 클래스가 있고, 자식 클래스가 있을 때, 자식 클래스가 부모 클래스의 메서드를 오버 라이드한다면 부모 클래스의 메서드가 호출될 지, 자식 클래스의 메서드가 호출될 지 런타임 때까지 모른다는 것이다.
public class Vehicle {
    public String name;
    public Vehicle(String name){
        this.name = name;
    }
    public void drive(){
        System.out.println("my car" + name + " bong");
    }
}
public class BMW extends Vehicle{
    public BMW(String name){
        super(name);
    }

    @Override
    public void drive() {
        System.out.print("BMW boo!");
    }
}
class Main
{
    public static void main (String[] args)
    {
        Vehicle car = new BMW("250d");
        Vehicle vehicle = new Vehicle("hyundai");
        vehicle.drive();
        car.drive();
    }
}

위의 예제에서 BMWVehicledrive메서드를 오버라이드 하였다. 그럼 Main에 있는 car, vehicledrive는 무엇이 실행될까 라는 것이다.
이는 컴파일 타임에서 해결할 수 없다. 지금이야 명시적으로 new 객체를 써주어서 그렇지, 함수의 인자로 인스턴를 받거나, 외부에서 받는다면 모를 수 밖에 없다. 따라서 런타임에서 결정해야하는 것이다.

이것을 런타임에서의 다형성이라고 한다.

지금은 함수를 예제로 들었지만 다형성은 프로그래밍 언어의 각 요소들(상수, 변수, 식, 객체, 함수, 메서드 등)이 다양한 자료형에 속하는 것이 허가되는 성질을 가리킨다.
즉, 같은 이름의 상수, 변수, 메서드, 함수, 객체 여도 그 내부적으로 어떤 인스턴스 타입을 가졌냐에 따라 동작을 달리할 수 있도록 한 것이다.

2. 선언적 프로그래밍(Declaretive Programming)

선언적 프로그래밍은 어떻게 해야한다고 알려주는 것이 아닌 '''해야할 것만 알려주고 어떻게 하는 지에 대해서는 신경쓰지 않는다는 것'''이다.

가령, 친구가 서울에서 부산으로 간다고 할 때, 명령형 프로그래밍은 부산으로가는 세세한 방법을 알려준다. 반면 선언적 프로그래밍은 부산에 가야한다느 것과 어느 지점으로 가면 쉽게 갈 수 있다는 정도만 알려주고
어떻게 그 지점까지 가야하는 지, 무엇을 타야하는 지에 대해서는 안알려주고 신경도 안쓴다.

즉, 선언적 프로그래밍해야할 것을 알려줄 뿐이지, 명령형 프로그래밍처럼 어떻게 해야할 지에 대해서는 알려주지 않는다.
이런 선언적 프로그래밍으로 대표적인게 바로 SQL이다. SQL은 '어떻게 가져와라'가 아닌, '이것을 가져와라' 이기 때문에 선언적 프로그래밍이라고 볼 수 있다.

3. 함수형 프로그래밍

함수형 프로그래밍선언적 프로그래밍의 하위 개념으로, 선언적 프로그래밍과는 달리 함수형 프로그래밍은 프로그램의 내부적인 상태를 바꾸지 않는다.

선언적 프로그래밍은 함수가 대게 **일련의 지시나 과정, 루틴```으로 여겨진다. 함수는 메모리에 있는 상태를 변경할 뿐만 아니라, 변경하기도 한다.
이러한 방식으로 멱등성이 보장되지 않는데, 즉, 같은 함수에 같은 매개변수를 넣어도 내부 상태에 따라 다른 결과가 나올 수 있다는 것이다.

함수형 프로그래밍수학적인 함수와 동일하다. 오직 매개변수에만 의존하며, 프로그램의 상태와 시간 등 다른 것들에 영향을 받지 않으므로 멱등성이 보장된다. 즉, 같은 인자에 대해서 같은 결과가 나온다는 것이다.

함수형 프로그래밍이 여러 사랑을 받는 이유는 병렬 환경에서 좋기 때문이다. 병렬적으로 프로그램이 작동하는 환경에서 함수형 프로그래밍은 오직 함수의 인자에만 의존하므로 결과를 예측하기 쉽다.
이 덕분에 병렬적으로 작동하는 프로그램에서 함수형 프로그래밍으로 만든 함수는 어디에서나 실행할 수 있으며 여러 병렬 실행의 결과를 결합하여 추가로 사용할 수 있다.

이렇게 함수형 프로그래밍에서 부수효과(side effect)가 없고, 같은 매개변수에 대해서 같은 결과를 돌려주는 함수를 '순수 함수'라고 한다.`

0개의 댓글