그만... 하고싶어도 끝이 없는게 프로그래밍 공부아닐까 싶다. 이제는 전공자보다 공부잘해! 라고 하는 친구들을 보면 부럽다. 나도 저렇게 속편했으면 하지만 아쉽게도 그런 인간이 못되나보다.
얼마전에 회사에서 디자인 패턴 관련 교육이 있었는데, 갑자기 기억이 안나서 예전에 배운 디자인 패턴이나 다시 복습해보려고 한다.
한가지 명심해야할 것이, 디자인 패턴은 만능의 레시피가 아니다. '디자인 패턴을 적용한 코드는 모두 좋고, 적용 못한 코드는 별로다' 라는 말은 잘못되었다. 대체로 좋은 경우들이 많지만, 빠르게 개발하는 상황에서 유지 보수 신경쓴다고 두 시간 째 고민만 하고 있는 것보다 그냥 1초만에 코드를 복붙하는 경우가 좋을 때도 있다. 그런 의미에서 객체지향에 반기를 든 프로그래밍 이론들과 언어들도 꽤 많다. ex) golang
추가적으로 디자인 패턴은 성능이 매우 구리다. 그래서 성능에 민감한 코드를 만들 때는 디자인 패턴을 남발하는 것은 좋지 않다. 이건 그냥 쭉 코드를 적은 것이 객체 지향 코드보다 성능이 좋다는 것만 봐도 알 수 있다. 따라서, 유지 보수가 중요한 관점인 코드인 경우는 디자인 패턴으로 만드는 것이 확실히 좋지만, 몇 번만 쓸 코드나 속도나 타이밍이 중요한 코드는 디자인 패턴에 집착하지 않는 것이 좋다.
해당 포스트는 java 또는 c++
과 같은 언어에 대한 기본적인 이해를 바탕으로 작성되었습니다.
객체는 객체지향 프로그래밍(OOP)의 주요 요소이다. 객체는 상태(state
)와 동작(behavior
)를 가진다.
어렵게 상태
와 동작
이라고 말하지만, 사실 상태
는 맴버 변수
를 말하고, 동작
은 메서드
를 말한다.
클래스를 템플릿으로 생각하면 객체는 템플릿(클래스)
의 구현이다. 가령, 인간이 인간이 가질 수 있는 행동과 속성을 정의하는 클래스라면, 나 그리고 모든 사람들은
인간이되기 위한 모든 요구사항을 충족했기 때문에 인간 클래스의 대상이다. 자동차로 생각하면 현대, BMW 등
의 자동차들이 되겠다.
하나하나 공부해보자
캡슐화는 기본적으로 속성(상태)
와 동작(메서드)
의 바인딩을 의미한다. 즉, 개체의 속성과 동작을
한 곳에서 유지 관리 및 확장이 쉽도록 한 것이다. 또한, 캡슐화는 사용자에게 불필요한 세부 정보를 숨기는 메커니즘을 제공한다.
java
에서는 클래스 사용자에게 표시되는 항목과 숨겨진 항목을 관리하기 위해 메서드 및 속성에 대한 액세스 지정자(public, private, protected, default
)를 제공할 수 있다.
정리하면 다음과 같다.
캡슐화는 객체지향언어의 가장 기본 원칙 중 하나이다. 클래스를 만드는 것 자체가 하나의 캡슐화
인 것이다. 그렇기 때문에
캡슐화는 다른 모듈을 분리하는 데 도움이 된다. 분리된 모듈은 독립적으로 개발 및 유지 관리할 수 있다.
Data Hiding(information hiding) vs Encapsulation
: 한 가지 오해하지 말아야할 것이 있는데,encapsulation
은 정보를 보호하기위해서 사용한다고 말하면 안된다.
아마 면접에서 그렇게 말하면 좋은 점수받기는 쉽지 않다. 이유는Data Hiding
과의 차이점을 설명하라면 애매해지기 때문이다.
Data hiding
은 클래스의 맴버인,속성(맴버변수)과 행동(메서드)
를 외부 사용자에게서 보호하기 위해 사용하는 개념으로, 접근 지시자(public, protected, private
)가 대표적이다.
반면,Encapsulation
역시도Data hiding
처럼 데이터를 보호한다는 개념이 있지만, 흩뿌려진속성과 행동
을 하나의 캡슐로 모아준다는 의미가 더 크다.
즉, 클래스를 만드는 것 자체가 흩뿌려진속성과 행동
을 바인딩시켜 하나의 틀로 만드는 것이므로캡슐화
라고 말할 수 있다. 여기에 더불어 지시자를 넣어 보안을 감안할 수 있다. 따라서,Data Hiding
은
Encapsulation
의 한 부분이며,Encapsulation
이 더 큰 개념이다.
참고로 분리된 모듈/클래스/코드가 외부 노출 동작에 영향을 주지 않고, 내부적으로 변경되는 기술을 코드 리팩토링
이라고 한다.
추상화는 캡슐화와 밀접한 관련이 있으며 어느 정도 중첩된다. 추상화
는 개체가 하는 일을 노출시키고, 개체가 그 일을 하는 방법을 숨기는 매커니즘을 제공한다.
가장 대표적인 예가 바로, 자동차이다. 운전을 하기위해 자동차 내부가 무엇이며 어떻게 작동하는 지 알 필요가 없다. 그러나, 자동차를 구동시키고 조작하는 데이터와 행동은 알아야 한다.
데이터는 자동차의 대시보드가 되며, 행동은 우리가 자동차를 운전하는데 사용하는 컨트롤러가 된다. 다시 말하지만, 우리는 그 내부적으로 어떻게 작동하는 지 알필요가 없다.
즉, 추상화는 어떤 객체의 속성과 행동
중에서 사용자에게 보여질 수 있는 지 추출하는 것이다. 그러나, 사용자가 그 내부의 동작에 대해서는 관심이 없도록 한다.
오직 어떻게 사용하는 지
가 중요하도록하지, 어떻게 동작한다
는 모르게 한다.
Data hiding vs Abstraction
:정보 은닉
과추상화
를 헷갈릴 수도 있는데,정보 은닉
은 말 그대로, 사용자에게 어떤 정보가 접근되지 말아야 하는 지를 결정하는 것이다. 즉, 보안을 위한 것이다.
반면,추상화
는 복잡성을 감추기 위한 것이다. 즉, 오로지 외부에 보여질 객체의 양상에 집중하는 것이다. 즉, 사용자가 보는 개념적인 경계에 집중하여 사용자가 원하는(관심있어하는) 정보나 중요하게 생각하는 부분만
제공하고, 관심이 없거나 알 필요없는 내용은 숨기는 것이다. 정리하자면,정보 은닉
은 데이터를 보호하기 위한 목적이다. 반면,추상화
는 사용자 원하는 속성, 행동을 노출시키고 관심없는 부분을 억눌러 사용자에게 단순함을 제공하고, 복잡성을 억누르는 것이다.
그래서,abstration method나 interface
는 내부구현이 없고, 사용자들이 자유롭게 접근할 수 있도록 한다. 이를 통해추상화
를 구현하여 사용자들에게 복잡성을 내리고, 단순함을 제공하는 것이다.
상속
은 객체 또는 클래스를 다른 객체 또는 클래스에 기반하는 기능이다. 즉, 자식 클래스는 부모 클래스의 속성과 행동을 물려받고, 추가적인 속성과 동작을 추가할 수 있다.
가령, 모든 자동차들은 이름
이라는 속성과 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();
}
}
BMW
는 Vehicle
을 상속하였기 때문에 Vehicle
이 가진 속성과 행동을 가진다. 때문에 name과 drive
를 사용할 수 있다. 또한, 추가적으로
vehicle
에는 없는 BMWDrive
를 사용할 수 있다. 이 처럼 객체는 상속
을 통해 자신을 확장해나가고, 추가적인 상태와 동작
을 구현해나갈 수 있다.
넓은 의미에서 다형성은 다른 유형의 엔티티(객체)에 대해 동일한 인터페이스를 사용할 수 있는 옵션을 제공한다.
다형성에는 컴파일 시간
과 런타임
이라는 두 가지 주요 유형이 있다.
shape
라는 클래스에 calc
매서드가 있는데, 매개변수를 하나만 주면 반지름
으로 인식하여public class Shape {
public double calc(int radius){
return radius*radius*3.14;
}
public int calc(int height, int width){
return height * width;
}
}
바로 다음과 같은 경우를 말하는 것이다. 오버로딩
과 같은 것은 컴파일 타임에 결정할 수 있으므로 큰 문제가 되지 않는다.
런타임 다형성
이 진짜 다형성이라고 한다. 이는 부모 클래스가 있고, 자식 클래스가 있을 때, 자식 클래스가 부모 클래스의 메서드를 오버 라이드
한다면 부모 클래스의 메서드가 호출될 지, 자식 클래스의 메서드가 호출될 지 런타임 때까지 모른다는 것이다.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();
}
}
위의 예제에서 BMW
는 Vehicle
의 drive
메서드를 오버라이드 하였다. 그럼 Main
에 있는 car, vehicle
의 drive
는 무엇이 실행될까 라는 것이다.
이는 컴파일 타임에서 해결할 수 없다. 지금이야 명시적으로 new 객체
를 써주어서 그렇지, 함수의 인자로 인스턴를 받거나, 외부에서 받는다면 모를 수 밖에 없다. 따라서 런타임에서 결정해야하는 것이다.
이것을 런타임에서의 다형성이라고 한다.
지금은 함수를 예제로 들었지만 다형성은 프로그래밍 언어의 각 요소들(상수, 변수, 식, 객체, 함수, 메서드 등
)이 다양한 자료형에 속하는 것이 허가되는 성질을 가리킨다.
즉, 같은 이름의 상수, 변수, 메서드, 함수, 객체
여도 그 내부적으로 어떤 인스턴스 타입을 가졌냐에 따라 동작을 달리할 수 있도록 한 것이다.
선언적 프로그래밍은 어떻게 해야한다고 알려주는 것
이 아닌 '''해야할 것만 알려주고 어떻게 하는 지에 대해서는 신경쓰지 않는다는 것'''이다.
가령, 친구가 서울에서 부산으로 간다고 할 때, 명령형 프로그래밍은 부산으로가는 세세한 방법을 알려준다. 반면 선언적 프로그래밍은 부산에 가야한다느 것과 어느 지점으로 가면 쉽게 갈 수 있다는 정도만 알려주고
어떻게 그 지점까지 가야하는 지, 무엇을 타야하는 지에 대해서는 안알려주고 신경도 안쓴다.
즉, 선언적 프로그래밍
은 해야할 것
을 알려줄 뿐이지, 명령형 프로그래밍
처럼 어떻게 해야할 지
에 대해서는 알려주지 않는다.
이런 선언적 프로그래밍
으로 대표적인게 바로 SQL
이다. SQL
은 '어떻게 가져와라'가 아닌, '이것을 가져와라' 이기 때문에 선언적 프로그래밍이라고 볼 수 있다.
함수형 프로그래밍
은 선언적 프로그래밍
의 하위 개념으로, 선언적 프로그래밍
과는 달리 함수형 프로그래밍
은 프로그램의 내부적인 상태를 바꾸지 않는다.
선언적 프로그래밍
은 함수가 대게 **일련의 지시나 과정, 루틴```으로 여겨진다. 함수는 메모리에 있는 상태를 변경할 뿐만 아니라, 변경하기도 한다.
이러한 방식으로 멱등성이 보장되지 않는데, 즉, 같은 함수에 같은 매개변수를 넣어도 내부 상태에 따라 다른 결과가 나올 수 있다는 것이다.
함수형 프로그래밍
은 수학적인 함수
와 동일하다. 오직 매개변수에만 의존하며, 프로그램의 상태와 시간 등 다른 것들에 영향을 받지 않으므로 멱등성이 보장된다. 즉, 같은 인자에 대해서 같은 결과가 나온다는 것이다.
함수형 프로그래밍
이 여러 사랑을 받는 이유는 병렬 환경에서 좋기 때문이다. 병렬적으로 프로그램이 작동하는 환경에서 함수형 프로그래밍
은 오직 함수의 인자에만 의존하므로 결과를 예측하기 쉽다.
이 덕분에 병렬적으로 작동하는 프로그램에서 함수형 프로그래밍
으로 만든 함수는 어디에서나 실행할 수 있으며 여러 병렬 실행의 결과를 결합하여 추가로 사용할 수 있다.
이렇게 함수형 프로그래밍
에서 부수효과(side effect)가 없고, 같은 매개변수에 대해서 같은 결과를 돌려주는 함수를 '순수 함수'라고 한다.`