diff --git a/_posts/2022-03-17-TWL-02-OOP-FP.md b/_posts/2022-03-17-TWL-02-OOP-FP.md new file mode 100644 index 0000000..23368e3 --- /dev/null +++ b/_posts/2022-03-17-TWL-02-OOP-FP.md @@ -0,0 +1,473 @@ +--- +layout: post +title: TWL-02 객체지향과 함수지향 +subtitle: 객체지향, 함수지향에 대해 알아봅니다. +categories: [TWL, CS] +tags: [TWL, CS, OOP, FP] +--- + +# 객체지향 프로그래밍 (OOP) +> Object Oriented Programming + +요즘은 어떤 프로그래밍 언어를 처음 시작하는지 모르겠지만 저는 첫 프로그래밍 언어로 `Java`를 배웠습니다. 커피를 마시면서 만들어서 `Java`라는 이름을 가졌다는 이야기와 VM을 사용하는 **객체지향적 +프로그래밍언어**라는 이야기를 처음 들으며 언어를 시작했고 이후에는 `Python`, `C`, `C++`의 언어들을 배우게 되었습니다. +(그 학생은 5년 뒤 Java를 쓰지 않고 `Rust`, `Go`를 사용하게 됩니다...) + +최근에는 Java를 사용하지는 않지만 객체지향적으로 개발하라는 말은 계속해서 듣고 있습니다. 결국 어떤 언어를 사용하는지보다는 어떻게 개발하는지가 더 중요한 것 같습니다. +그러면 어떤 장점이 있어서 객체지향적으로 개발하라는 걸까요? + +## 객체지향적으로 개발하라? + +먼저 객체지향적으로 개발한다는 의미를 알아야 합니다. 객체 지향적으로 개발한다는 것은 실제 세상처럼 객체들의 집합으로 생각하고 프로그래밍한다는 것입니다. +간단한 예를 들자면 강아지, 책상과 같은 객체에서의 값(나이, 책상서랍 등)과 동작(짖다, 서랍에 넣다 등)을 객체에서 갖도록 해서 좀 더 코드를 이해하기 쉽게 만드는 것입니다. +보통 우리가 다른 사람에게 무언가 설명할 때 현실에 있는 다른 무엇가를 비유해서 설명하는 것처럼 코드도 객체에 비유해서 쉽게 이해할 수 있도록 하는 것이죠. + +개발하다보면 동작이 얼마 없을 때는 문제 없지만 동작이 점점 커지면서 각 동작을 작은 단위로 분리해야하는데, +이때 어떤 객체에서 무슨 작업을 할 지 이름만으로 쉽게 알 수 있어 이런 방법은 합리적으로 보입니다. +물론 객체로 감싸는 과정에서 어느정도 오버헤드가 발생하기는 하지만 성능이 너무 중요한 상황이 아니라면 +충분히 감당 가능한 정도입니다. 어느정도 메모리, cpu 낭비를 하더라도 더 읽기 쉽고 생산성 있는 코드가 더 중요한 것입니다. +(물론 낭비하는 자원이 감당 가능한 레벨이어서 가능한 겁니다) + +### 절차적 언어 +> Procedural Programming (프로시저 프로그래밍) + +객체 지향적으로 개발하라고 말할 때 주로 비교되는 것은 절차적 프로그래밍(`procedural programming`)이었지만 +최근에는 함수형 프로그래밍(`functional programming`)이 주로 비교되는 것 같습니다. +> `procedural programming`은 사실 프로시저(함수) 프로그래밍에 가깝지만 (직역한) 번역명으로 더 잘 알려진 절차적 프로그래밍이라고 적겠습니다 +> +> `oriented`가 없는데 절차 "지향"적 언어라고는 못 적겠습니다. + +먼저 절차적 프로그래밍부터 알아보면 **"코드를 순서대로 작성하고 실행하는 언어"** 입니다. +사실 객체지향적으로 개발하면서도 내부적으로는 어떠한 절차를 거쳐서 개발하기 때문에 완전히 다른 개발 방법이라고 하기에는 문제가 있습니다. +객체지향적 프로그래밍 방법과 절차적 프로그래밍의 가장 큰 차이점은 데이터와 함수가 어떻게 묶이는지 입니다. + +```c +void move(int* x, int* y, int move_x, int move_y) { + *x = *x + move_x; + *y = *y + move_y; +} + +int main() { + int my_x = 0; + int my_y = 0; + // 내 위치를 (2, 4) 만큼 이동한다. + move(&my_x, &my_y, 2, 4); + return 0; +} +``` + +절차적 언어인 c 언어에서는 함수가 정해져있고 데이터를 입력으로 보냅니다. +{: style="text-align: center; color: gray; margin-top:.5em;"} + +절차적 언어로 가장 유명하고 현재도 많이 사용되는 c 언어로 예를 들어보면 어떠한 동작을 하는 함수를 구현하고, +함수에 입력으로 값이나 포인터를 넘겨 연산을 순서대로 실행하도록 개발합니다. + +#### 추상화 +> 모든 프로그래밍 방법론은 추상화에서 시작된다. + +절차적 언어인 c에서는 추상화가 없다고 할 수는 없습니다. +추상화라는 것은 구체적으로 작업을 표현하는 것이 아닌 핵심적인 개념만을 표현하는 것입니다. +위의 코드에서는 `x, y를 조작하는 구체적인 작업`이 아닌 `move라는 핵심적인 동작(함수명)`만으로 사용할 수 있도록 추상화 한것입니다. +이후에는 어떻게 동작하는지는 함수명과 설명에 대한 신뢰를 가지고 `move` 함수를 호출해 사용할 수 있습니다. +> 물론 실제 구현이 설명과 다르다면 문제가 발생합니다. + +내부적인 구현을 정확히 이해하지 않고도 사용할 수 있다는 점이 구체적인 동작을 그대로 사용하지 않고 추상화하는 이유 중 하나입니다. + +```c +void move(int* x, int* y, int move_x, int move_y) { + *x = *x + move_x; + *y = *y + move_y; +} + +void jump(int* x, int* y, int move_x, int move_y) { + *x = move_x; + *y = move_y; +} + +int main() { + int my_x = 0; + int my_y = 0; + // move 대신 jump 하도록 수정 + // move(&my_x, &my_y, 2, 4); + jump(&my_x, &my_y, 2, 4); + return 0; +} +``` + +추상화된 동작은 대체하기도 쉽다. +{: style="text-align: center; color: gray; margin-top:.5em;"} + +연산을 추상화하게 되면 사용할 때 `전체적인 로직에 대해서 쉽게 이해`할 수 있습니다. +`main` 안에서 `my_x`와 `my_y`를 단순히 더하거나 빼는 연산보다는 +`move`라는 함수 안에 로직을 넣고 `main`에서는 `move` 함수를 호출하는 것이 어떤 작업을 하는 건지 쉽게 이해할 수 있습니다. + +뿐만 아니라 추상화된 작업은 `다른 작업으로 쉽게 변경할 수 있습니다`. +현재는 코드를 `move`하는 동작으로 사용하고 있지만 만약 `jump`하는 동작으로 수정되어야 한다면 호출하는 함수를 변경하는 것으로 +쉽게 다른 동작으로 변경할 수 있습니다. + +비즈니스 동작이 달라지거나 최적화가 필요하거나 혹은 업데이트를 하는 등 여러 이유로 코드는 변경될 가능성을 가지고 있습니다. 그런 점에서 +추상화를 통해 이전 코드를 간단하게 대체할 수 있다는 것은 굉장한 장점입니다. + +절차적 언어에서는 함수 단위의 추상화를 제공하고 있습니다. 만약 어셈블리 언어로 +개발했다면 함수를 통해 정해진 연산을 호출하는 작업은 생각하지 못했을 것입니다. 하지만 c언어에서는 구체적인 연산을 매번 사용하지 않고 +함수로 호출할 수 있도록 추상화를 제공하고 있습니다. +> 절차적 프로그래밍은 함수로 묶는 정도의 수준으로 함수형 프로그래밍과는 다릅니다 + +어떠한 **"절차"** 에 대한 추상화만을 제공하는 것입니다. + +### 객체지향 +> 객체로 모든 것을 생각해 프로그래밍 하는 것 + +그렇다면 이제 객체지향에서의 추상화에 대해 조금 더 쉽게 이해할 수 있을 것 같습니다. 절차적 프로그래밍에서는 절차를 함수로 추상화했다면 객체지향 프로그래밍에서는 +객체를 추상화합니다. c언어에서 절차에 대한 추상화 기능으로 함수 기능을 제공해 절차적 언어라고 말하는 것처럼, +객체지향 언어는 객체를 추상화할 수 있도록 기능을 제공하는 언어를 말합니다. +> c언어에서 객체지향처럼 흉내낼 수는 있지만 객체지향 언어라고 하기에는 기능이 부족합니다. + +앞서 설명했던 예시를 객체지향적으로 수정해보겠습니다. + +```java +interface Mover { + void move(int moveX, int moveY); +} + +class Human implements Mover { + private int x; + private int y; + + public Human(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public void move(int moveX, int moveY) { + this.x += moveX; + this.y += moveY; + } +} + +class Jumper implements Mover { + private int x; + private int y; + + public Jumper(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public void move(int moveX, int moveY) { + this.x = moveX; + this.y = moveY; + } +} + +public class Main { + public static void main(String[] args) { + Mover mover = new Human(0, 0); + Mover jumper = new Jumper(0, 0); + mover.move(2, 4); + jumper.move(2, 4); + } +} +``` + +main에서는 추상화된 객체의 동작만이 실행된다. +{: style="text-align: center; color: gray; margin-top:.5em;"} + +객체지향 프로그래밍에서는 함수만 추상화하는 것이 아니라 사용되는 값도 함께 객체로 추상화합니다. +동작은 클래스에서 구현하지만 사용할 때는 인터페이스의 추상화된 함수를 호출합니다. (객체와 동작의 추상화) +또한 추상화된 함수에서 다룰 값도 함께 객체에서만 접근할 수 있도록 만들어 외부 동작에 의해 +추상화된 로직이 깨지지 않도록 만들 수 있습니다.(캡슐화) + +객체에 대해 추상화가 되었기 때문에 이전에 함수를 다른 기능의 함수로 대체했듯이 +다른 작업을 하는 객체로도 대체할 수 있습니다. + +### 객체지향의 특징 + +기본적으로 꼽히는 객체지향의 특징은 다음과 같습니다. + +1. 추상화 + * 객체를 추상화해 복잡한 로직이 아닌 객체, 함수를 통해 핵심적인 기능만을 볼 수 있도록 합니다. +2. 캡슐화 + * 데이터를 객체 내부에 저장해 접근제어자(`private`, `public`)를 통해 외부에서 조작하지 못하도록합니다. + * 추상화된 로직을 사용할 때 내부 데이터를 외부에서 접근한다면 정해진 로직이 깨질수 있습니다. +3. 상속 + * 상위 클래스의 필드와 연산을 하위클래스에서 가질 수 있습니다. + * 최근에는 상속의 문제점들로 인해 상속을 지원하지 않는 언어들이 보이고 있습니다. (`rust`, `go`) + * 대신 내부 필드로 확장할 객체를 가지는 `composition`으로 구현합니다. +4. 다형성 + * 같은 이름의 함수를 다양한 타입이나 다양한 구현(`overriding`)으로 + * `overloading`, `overriding`을 이용해 다형성이 발현됩니다. + * `overriding`하면서 `Dependency Injection(DI)`을 가능하게 하는 기본 성질입니다. + * 테스트 코드의 mocking 에도 사용되는 성질입니다. + +#### 객체지향의 추상화 + +실생활에서 우리는 여러 전자 기기를 사용하지만 내부적으로 어떻게 동작하는지 모릅니다. 어떻게 돌아갈지 짐작만 하고 있을 뿐이죠. +대부분 사용설명서를 읽고 원하는 기능을 찾아 기기를 사용합니다. 추상화하는 것도 동일합니다. 우리는 구현할 때와 사용할 때를 구분해서 +생각해야합니다. 절차적 프로그래밍의 함수나 객제지향 프로그래밍의 객체를 구현할 때는 전자기기를 만드는 것과 마찬가지로 +상태가 어떻게 관리되고 어떻게 동작하는 지 알고 있어야 합니다. 하지만 구현된 함수나 객체를 사용할 때는 전자기기를 사용하는 것처럼 +굳이 내부에 대해 모르더라도 사용할 수 있어야 합니다. +> 사용 설명서를 읽지도 않고 어림짐작으로 사용하던 모습은 api 설명을 안 보고 함수명만 보고 사용하는 모습과 비슷합니다 + +앞서 말했지만 추상화는 객체지향에서만의 특징은 아닙니다. 절차적 프로그래밍에서도 추상화가 들어있습니다. +다만 추상화하는 정도나 대상이 다를 뿐입니다. + +절차적 프로그래밍에서 코드를 연산 레벨에서 추상화 했다면, 객체지향 프로그래밍에서는 코드를 객체 레벨에서 추상화하게 됩니다. +절차적 프로그래밍에서는 `어떤 상태를 받아 연산을 하는 함수` 로 추상화하고, 객체지향 프로그래밍에서는 `상태도 연산과 함께 추상화` 해서 +객체에서 관리합니다. 상태를 함께 추상화한다는 것은 +추상화된 인터페이스를 사용할 때 `상태가 어떤 방식으로 관리되는지 모르더라도 사용할 수 있음`을 의미합니다. + +예를 들어 절차적 프로그래밍에서 함수를 정의해 연산을 추상화하고, 함수를 호출해서 사용함으로써 내부 연산을 모르더라도 사용할 수 있지만 +입력으로 전달하는 `struct` 값들은 따로 관리하고 있어야 합니다. + +하지만 객체지향 프로그래밍을 하면 상태에 대한 관리도 객체에서 함으로써 객체를 사용할 때 더이상 상태값이 어떻게 관리되는지 +모르더라도 사용하는 데 문제가 없습니다. 상태값을 어떻게 관리할 지는 객체를 구현할 때의 문제입니다. + +예를 들어 처음 파이썬 또는 자바스크립트를 공부할 때, `dictionary`가 내부적으로 어떻게 구현되어있는지 모르더라도 +`key`로 값을 저장하고 가져온다는 사실만 알고 있으면 `dictionary`를 사용할 수 있습니다. 추상화하면 (성능은 잠시 미뤄두고...) +객체의 구현에 대해 모르더라도 `사용하는데는 문제가 없습니다`. 그 후 `dictionary`의 구현을 Hash를 이용할 것인지 Tree를 +이용할 것인지는 `구현의 문제`입니다. + +#### 캡슐화, 정보은닉 + +캡슐화는 객체의 상태나 연산을 해당 객체에서만 접근할 수 있게해서 외부 연산에 의해 오류가 발생하지 않도록 합니다. +Java나 C++에서 제공하는 `private`, `protected`, `public` 접근제어자를 통해 객체, 상속받은 객체, 외부에서 +접근하는 것을 제어하며, golang은 대문자, 소문자를 이용하고, Rust에서는 `pub` 접근 제어자를 이용합니다. +> 파이썬은 `_`를 앞에 붙여 접근제어자처럼 사용하지만 실제로 접근은 가능하기에 접근제어자가 없어 완벽한 객체지향언어가 아니라는 말이 나옵니다. + +연산이 복잡해지게 되면 함수를 계속 생성하게 되는데 이때 객체 내부에서만 재활용할 연산들은 `private`으로 객체 내부에서만 +접근할 수 있도록 두어 외부 인터페이스와 내부에서 사용할 함수를 구분하는데 주로 사용합니다. + +#### 상속 + +최근 언어들에서는 클래스 상속 기능을 제공하지 않는 언어들이 많습니다. 상속은 상위 클래스에서 구현한 함수나 필드를 하위 클래스에서도 +물려받는 것을 의미합니다. 상위 클래스에서 구현한 기능을 따로 구현하지 않더라도 하위 클래스에서 재사용할 수 있다보니 +구현하는 측면에서 재사용성이 늘어나는 장점이 있습니다. + +하지만 상속으로 구현하게 되면 하위 클래스에서 함수를 오버라이딩하거나 오버로딩할 수 있고, 혹은 여러 클래스를 다중 상속받으면서 +하위 클래스에서 상위 클래스의 기능을 깨뜨리는 경우도 발생합니다. 자세한 내용은 이후 객체지향 원칙에서 설명하겠지만 +상속의 문제점들로 인해 최근에는 상위 클래스를 직접 상속받는 것이 아닌 필드로 받아 사용하는 `composition` 방식으로 주로 구현하고 있습니다. + +하지만 문제를 발생시키지 않는 인터페이스를 `implements`하는 기능들은 go(`interface`)와 rust(`trait`)에서 제공합니다. +아무래도 인터페이스의 구현은 객체지향 프로그래밍에서 가장 중요한 역할을 하기 때문에 최근에 나온 언어들에서도 제공되는 것이 아닌가 싶습니다. + +#### 다형성 + +함수 이름은 같지만 다양한 타입이나 구현을 가질 수 있는 성질입니다. 기본적으로는 함수이름이 같지만 다른 타입의 파라미터와 리턴값을 가지는 +`overloading`과 하위 클래스에서 함수를 재작성하는 `overriding`으로 이루어집니다. + +최근 언어에서는 `overloading`은 오히려 코드를 읽는데 헷갈릴 수 있다는 이유로 지원하지 않는 언어도 있지만 `overriding`은 다릅니다. +최소한 `interface`의 함수를 `overriding`할 수 있도록 기능을 제공하고 있습니다. +각 클래스에서는 구현한 클래스 타입을 필드로 가지는 것이 아니라 `interface`를 타입으로 필드를 가집니다. +그 후에 사용할 때 구현 클래스를 대입해 `interface`로 호출하지만 대입한 구현 클래스를 실행시키는 효과를 얻을 수 있습니다. + +이를 통해 다른 기능을 사용할 때 다른 구현 클래스를 대입해 사용해 내부 구현을 변경하지 않더라도 다른 기능을 실행할 수 있고, +함수의 인자로 `interface`를 받는다면 함수의 구현을 변경하지 않더라도 외부에서 원하는대로 함수가 동작하도록 구현 클래스를 넘기는 +`Dependency Injection(DI)`을 할 수도 있습니다.: + +### 객체지향의 원칙 +> 어떻게 개발해야 잘 개발하는 것인가? + +그럼 객체지향적으로 개발할 때 무슨 원리를 기반으로 개발하고 있을까요? 바로 SOLID입니다. + +* S: SRP: Single Responsibility (단일 책임의 원칙) + * 하나의 객체는 하나의 책임(기능, 역할)만을 가져야합니다. +* O: OCP: Open-Closed (개방 폐쇄의 원칙) + * 기능 확장에는 열려있고, 수정(구현 코드)에는 닫혀있어야합니다. +* L: LSP: Liskov Substitution (리스코프 치환의 원칙) + * 부모 클래스의 인스턴스 위치에 자식 클래스를 두더라도 문제가 없어야 합니다. + * 여기서 상속이 문제를 발생시킵니다. +* I: ISP: Interface Segregation (인터페이스 분리의 원칙) + * 클래스 내에서 사용하지 않을 인터페이스(함수)는 구현하지 않아야 합니다. + * 각 인터페이스를 최소화하여 분리합니다. +* D: DIP: Dependency Inversion (의존 역전의 원칙) + * 상위 모듈은 하위 모듈에 의존하지 않고 추상화가 세부 구현(인터페이스에 없는 함수나 특정 구현 클래스)에 의존하지 않아야 합니다. + * 추상화하는 함수가 primitive 타입이나 인터페이스를 받아 구현하는것은 괜찮지만 특정 구현 클래스를 받는 것은 지양해야 합니다. + +#### SRP: Single Responsibility (단일 책임의 원칙) +> 하나의 객체는 하나의 책임(기능, 역할)만을 가져야합니다. +> 객체만이 아니라 모듈이나 함수를 구현할 때에도 마찬가지입니다. +> 한 객체가 하나의 함수만 가져야만 한다는 것은 아닙니다. + +단일 책임의 원칙은 하나의 객체 또는 함수에서 하나의 역할만을 하도록 구현해야 한다는 원칙입니다. +클래스를 통해 개발할 때 주로 발생하는 문제는 하나의 객체가 너무 커진다는 것입니다. +주로 기능을 추가하다보면 필요한 기능을 하나 둘씩 편의를 위해 기존에 있는 객체에 함수로 추가하게 되고 +점점 객체는 특정 하나의 역할만을 하는 것이 아닌 모든 것에 다재다능한 슈퍼 객체가 되어버립니다. + +```java +class SuperDataViewer { + // 비슷한 작업의 함수만 늘어가면서 객체가 비대해짐 + public void viewHtml() { + ... + } + + public void viewMarkDown() { + ... + } + + public void viewCSV() { + ... + } + + // Viewer에서는 보여주는 작업만 하고 update는 Viewer를 사용하는 클래스에서 구현하는 것이 옳음 + public void updateView() { + ... + } +} + +interface DataViewer { + void view(); +} + +class HTMLViewer implements DataViewer { + @Override + public void view() { + ... + } +} +``` + +이것은 사실 객체지향적으로 프로그래밍한 코드라고 볼 수 없습니다. 객체 안에 절차적 프로그래밍의 함수를 때려넣은 것 뿐이죠. +주로 주의할 부분은 하나의 객체에서 비슷한 기능을 하는 작업을 함수만 늘려서 구현하거나 하나의 클래스의 역할을 너무 크게 생각해 +너무 많은 기능을 넣은 경우입니다. + +위의 예에서는 각각 다른 데이터를 보여주는 상황으로 `HTMLViewer`, `MardownViewer`, `CSVViewer`와 같은 방식으로 +각각 다른 클래스에서 `view`함수를 구현하는 방식으로 구현하는 것이 더 좋아보입니다. +그리고 `updateView`와 같은 `view`를 다루는 상위 작업은 `DataViewer`를 사용하는 클래스에서 구현해두면 +`DataViewer`는 `view`라는 작업에만 집중할 수 있어 역할이 더 명확해집니다. +`update`기능 구현이 `DataViewer`의 클래스에 있는게 구현이 조금 더 편하다는 이유로 분리되지 않는다면 +점점 거대해진 모든 역할을 하는 클래스가 추가될 수 있습니다. + +#### OCP: Open-Closed (개방 폐쇄의 원칙) +> 기능 확장에는 열려있고, 수정(구현 코드)에는 닫혀있어야합니다. +> 기능 추가는 쉽게 할 수 있지만 기존 코드는 건드리지 않아야 합니다. + +개방 폐쇄의 원칙은 기능을 추가하더라도 기존 코드를 건드리지 않아야한다는 원칙입니다. +새로운 기능을 추가한다는 이유로 기존 코드를 건드리게 되면 멀쩡히 동작하던 기능이 갑자기 동작하지 않을 수도 있습니다. +앞서 보여드렸던 코드가 이 문제를 해결하는 방법이 될 수 있습니다. + +```java +class RealTimeDataViewer { + private final DataViewer dataViewer; + + RealTimeDataViewer(DataViewer dataViewer) { + this.dataViewer = dataViewer; + } + + void updateView() { + ... + this.dataViewer.view(); + } +} + +interface DataViewer { + void view(); +} + +class HTMLViewer implements DataViewer { + @Override + public void view() { + ... + } +} + +class MarkDownViewer implements DataViewer { + @Override + public void view() { + ... + } +} +``` + +만약 MarkDown을 보여주는 viewer를 제공하기 위해 기존 구현을 건드린다면 객체가 너무 비대해져 코드를 읽기 어렵거나 +기존 코드를 건드리면서 예상치 못한 버그를 발생시킬 수 있습니다. +애초부터 다른 객체에 새로운 기능을 구현하고 해당 기능을 사용하면 됩니다. + +다른 클래스에 구현해서 사용하면 당연히 확장에도 열려있고 수정에도 닫혀있겠지만 그것만으로는 사용하는 클래스의 코드를 변경하는 문제가 발생합니다. +개방 폐쇄의 원칙은 단순히 클래스만 따로 만들라는 것이 아닙니다. 클래스를 따로 만들지만 같은 인터페이스를 `implements`함으로써 +사용하는 곳에서는 다른 구현체만 넘겨주어 수정된 구현을 사용할 수 있게 하라는 것입니다. + +Java에서 `List` 인터페이스가 있지만 원하는 상황에 따라 다른 성능을 위해 `List list = new ArrayList<>();`처럼 +각기 다른 `ArrayList`, `LinkedList`를 사용하는 상황과 비슷합니다. 원하는 `List` 형태가 있다면 직접 구현해서 +기능을 확장할 수 있지만 기존에 있는 다른 형태의 `List`에 전혀 영향을 주지 않죠. +> 인터페이스 설계부터 잘 고려해서 작성해야 개방 폐쇄의 원칙을 지킬 수 있게 됩니다. + +#### LSP: Liskov Substitution (리스코프 치환의 원칙) +> 부모 클래스의 인스턴스 위치에 자식 클래스를 두더라도 문제가 없어야 합니다. + +보통 클래스를 구현할 때 구현한 클래스는 `어떤 조건을 만족해야한다`는 가정을 두는 경우가 많습니다. +예를 들어 + +#### ISP: Interface Segregation (인터페이스 분리의 원칙) +> 클래스 내에서 사용하지 않을 인터페이스(함수)는 구현하지 않아야 합니다. +> 인터페이스를 만들 때부터 최소한의 함수만을 갖도록 합니다. + +```java +interface IO { + int write(byte[] b); + byte[] read(); +} + +class FileReader implements IO { + @Override + public int write(byte[] b) { + return 0; + } + + @Override + public byte[] read() { + ... + return b; + } +} +``` + + +#### DIP: Dependency Inversion (의존 역전의 원칙) +> 상위 모듈은 하위 모듈에 의존하지 않고 추상화가 세부 구현(인터페이스에 없는 함수나 특정 구현 클래스)에 의존하지 않아야 합니다. + + +### 객체지향의 문제는? + +객체지향 프로그래밍을 보면 알 수 있듯이 객체에 `상태`를 두어 + +# 함수형 프로그래밍? +> 절차적 프로그래밍의 함수와는 함수의 `급`이 다르다. + +함수형 1급 객체 + +## 선언형 프로그래밍, 명령형 프로그래밍 + + + +### 함수형 언어? + + +## 함수형 프로그래밍의 특징 + +### 순수 함수 (side effect가 없는 함수) + +### 불변 데이터 + +### 일급함수 + +### 참조 투명성 + + +* 하스켈과 다른 언어에서의 http server 예시를 들어볼까? +* + +명령형 프로그래밍 + +선언형 프로그래밍 + + +## Reference + +* [http://www.incodom.kr/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5](http://www.incodom.kr/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5) +* [https://velog.io/@phs880623/%EA%B0%9D%EC%B9%98-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D](https://velog.io/@phs880623/%EA%B0%9D%EC%B9%98-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D) +* [https://coding-factory.tistory.com/328](https://coding-factory.tistory.com/328) +* [https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design](https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design) +*