본문 바로가기

Frontend Study - 2/Programming paradigm

프로그래밍 패러다임. 객체지향 프로그래밍과 함수형 프로그래밍.

프로그래밍 패러다임

: 프로그래밍을 하는 방식. 어떤 관점으로 개발을 할 건지에 대한 개발 방법론.

 

여러 프로그래밍 패러다임이 있지만, 자주 비교되는 객체지향 프로그래밍함수형 프로그래밍에 대해 공부해 보았다.

관련 글이 많이 있긴 한데 애초에 어려운 개념 인건지, 설명이 다 어렵게 되어있었다. 읽고 또 읽어서 이해한 뒤에 나름 정리해 보았다.

 

 

객체지향형 프로그래밍

 

우선 객체란 무엇인가? 

자주 들어보기도 하고 자주 쓰기도 했던 자바스크립트의 그 객체가 먼저 생각났다.

let kangchul = { name : "이강철",
		age : 32 ,
		sayHello : function() {console.log("hello")},
		}

자바스크립트를 사용하며 썼던 데이터 구조이다. key와 value값으로 구성되어있고 함수도 넣을 수 있다.  

 

사전을 검색해 보니 객체란 '클래스에서 정의한 것을 토대로 메모리에 할당된 것으로 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미하며, 변수, 자료 구조, 함수 또는 메소드가 될 수 있다.' 라고한다.

 

역시 어려운데,  어찌됐건 객체 지향이라는 것은 저런 '객체' 라는 것을 '지향' 한다는 것이다.

저런 것을 이용해서 프로그래밍 한다는 것은 알겠는데 잘 와닿지 않는다.

 

조금 더 잘 이해할 수 있으려면 객체지향의 대표적인 특징인 클래스를 이용한 객체 생성 방법을 들여다 보아야 한다. 

 

https://velog.io/@zzangzzong/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8DObject-Oriented-Programming

크게 이해해 보자면 클래스 라는 것은 위의 그림 처럼 하나의 틀, 하나의 프레임이다. 유명한 예시 중에 '붕어빵 틀'이 있다. 클래스라는 틀을 만들어 두고 안에 내용물을 변경해서 원하는 결과물(객체, 즉 인스턴스)를 쉽게 찍어내는 것이다. 비슷한 여러개의 결과물이 필요할 때 일일이 처음부터 하나하나 다 만드는 것보다 틀이 있기 때문에 시간과 노력이 절감된다. 

 

예시)

let kangchul = { name : "이강철",
		age : 32 ,
		sayHello : function() {console.log("hello")},
		}
let chulmin = { name : "이철민",
		age : 23 ,
		sayHello : function() {console.log("hello")},
		}
let sana = { name : "이사나",
		age : 22 ,
		sayHello : function() {console.log("hello")},
		}

 

위와 같은 객체들이 필요하다고 가정해보자. 하나 하나 직접 만들어 내는 것은 비효율적이다.

class를 활용한다면 객체들을 쉽게 생성해 낼 수 있다. 

 

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
   console.log("hello")
  }
}


const kangchul = new Person('이강철', 32);
const chulmin = new Person('이철민', 23);
const sana = new Person('이사나', 22);

 

클래스로 틀을 만들어 놓고, 들어가는 인자만 변경시킴으로서 객체(인스턴스)를 쉽게 생성한다.

 

 

결과적으로 객체지향 프로그래밍을 정리해 보자면

 

우선 객체들을 쉽게 만들어 내기 위한 틀을 만든다.

틀을 만들기 위해서 프로그래밍에 필요한 객체들을 파악하고 틀안에 각각의 역할을 정의한다. 변수가 필요하다면 변수를 정의하고 행위가 필요하다면 처리부분(함수)를 정의하는 것.

틀이 완성 되었다면 원하는 인자들을 기입하여 새로운 객체를 쉽게 생성해서 사용한다. 

그 객체들이 서로 상호 작용 하며 결과물을 만들어 낼 수 있게 된다.

 

 

객체지향의 특징

 

추상화, 캡슐화, 상속성, 다형성이라는 특징이 있다.

 

추상화 - 공통으로 가지는 속성, 기능들을 핵심적인 이름으로 대표하게 하는 것. ex) 사자, 코끼리, 늑대 를 '동물'로 묶는다.

캡슐화 - 데이터 구조와, 데이터를 다루는 방법을 결합시켜 묶고(변수와 함수를 함께 묶고), 밖에서 접근 불가능하게 은닉화하는 것.

상속성 - 상위 클래스의 특성을 하위클래스가 이어받아 추가, 확장하는 것. 재사용, 계층적 관계 형성, 유지보수 측면에서 중요하다.

다형성 - 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것. ex) 오버로딩, 오버라이딩.

 

a. 오버로딩 : 같은 이름을 가진 메소드를 여러 개 두는 것. 메소드의 타입, 매개변수의 유형, 개수 등으로 여러 개를 사용할 수 있다. 같은 이름의 메소드 이지만 인자를 다른 것을 받음으로서 다른 기능을 실행하게 한다. 

b. 오버라이딩 : 상위 클래스로부터 상속 받은 메소드를 하위 클래스가 덮어 씌우는 것. 재정의 하는 것. 

 

 

객체지향 SOLID 설계 원칙

 

SRP(Single Responsibility Principle) 단일 책임 원칙

모든 클래스는 각각 하나의 책임만 가져야 한다. 클래스는 그 책임을 완전히 캡슐화해야 한다.

덧셈에 대한 클래스가 있다면, 그 클래스는 덧셈에 관한 기능만 담당해야 한다. 

 

OCP(Open Closed Principle) 개방-폐쇄 원칙

확장에는 열려있고 수정에는 닫혀있는. 기존의 코드를 변경하지 않으면서(Closed), 기능을 추가할 수 있도록(Open) 설계가 되어야 한다는 원칙을 말한다.

 

LSP(Liskov Substitution Principle) 리스코프 치환 원칙

자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙이다. 즉 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다.

자식클래스는 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행하도록 해야 LSP를 만족한다.

 

ISP(Interface Segregation Principle) 인터페이스 분리 원칙

하나의 일반적인 인터페이스보다 여러개의 구체적인 인터페이스를 만들어야 하는 원칙.

나의 큰 인터페이스를 상속 받기 보다는 인터페이스를 구체적이고 작은 단위들로 분리시켜 꼭 필요한 인터페이스만 상속하자는 의미입니다. SRP가 클래스의 단일책임을 강조했다면 ISP는 인터페이스의 단일책임을 강조

 

DIP(Dependency Inversioni Principle) 의존 역전 원칙

의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존해야 한다는 것.

구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺으라는 것이다.

 

예시)

class 아이폰 {
fun 전화(){
     print("📞📞📞")
   }
fun 검색(){
     print("🔎🔎🔎")
   }
}
class 사용자 {
val 내폰 = 아이폰()
fun 전화(){
    내폰.전화()
   }
fun 검색(){
    내폰.검색()
   }
}

아이폰 클래스는 구체적인 클래스이기 때문에 변화에 취약하다.

만약 사용자가 아이폰을 다른 스마트폰으로 바꾸고 싶어한다면 코드에 상당한 변화가 필요할 것이다.

 

그래서 -> 아이폰 클래스를 덜 구체적인 추상화된 클래스(스마트폰) 로 만드는 것.

 

interface 스마트폰 {
    fun 전화()
    fun 검색()
}
class 아이폰 : 스마트폰 {
override fun 전화(){
    print("📞📞📞")
    }
override fun 검색(){
    print("🔎🔎🔎")
    }
}
class 사용자(내폰 : 스마트폰){
fun 전화(){
    내폰.전화()
    }
fun 검색(){
    내폰.검색()
    }
}
//실제 사용
val 나 = 사용자(object : 아이폰())
나.전화()
나.검색()

 

함수형 프로그래밍

 

사전적 정의는

'자료처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임'.

이다. 역시 와닿지도 않고 어렵다.

 

그나마 가장 이해가 되었던 정의는

'수학에서 주어진 데이터를 처리해 결과값을 도출하듯,

내 외부 소통이 한정된 파이프라인(=함수)들을 묶어가며 프로그램을 구성해 나가는 방법.'

 

이다. 아래 예시와 함께 보면 조금 더 이해가 된다.

 

const stringNumber = '12345';

console.log(
  stringNumber
    .split('')
    .map(v => Number(v))
    .reduce((sum, num) => (sum += num))
); // 15

 

기본값을 함수안에 넣고

그 결과 값을 또 다른 함수안에 넣고

이렇게 함수를 통해 데이터를 변경해가며 원하는 결과값을 얻어낸다. 

 

이 때 함수형 프로그래밍의 특징을 알 수 있는데 기존데이터, 상태의 변경을 최대한 피하면서 프로그래밍 한다는 것이다.

위의 예시에서도 원본 데이터 stringNumber라는 값을 통해 결과값을 얻어냈지만, 원본 데이터 자체는 그대로 유지된다. 

 

데이터 변경이 필요하다면, 원본 데이터가 아닌 복사본을 만들어서 그 복사본을 통해 변경을 작업을 진행한다. 

 

 

두번째 특징은 선언적이라는 것. 

'무엇을' 할 건지에 중점을 두는 프로그래밍이다. '어떻게' 할 지는 컴퓨터에게 맡긴다.

절차적으로 for문을 사용해 값을 하나하나 대입하는 것과 다르게 과정을 생략한다. 

 

 

세번째 순수함수

- 순수함수 : 출력이 입력에만 의존하는 함수.

const name = "강철"

const add = (a,b) => {
	return a+b
    }

함수 add는 매개변수 a,b에만 영향을 받는다. 예를 들어 전역 변수로 선언된 name 이라는 변수가 return 값에 포함되었다면 add 함수는 순수함수가 아니게 된다. 순수함수가 되기 위해서는 함수의 출력이 매개변수의 영향 받아야 한다. 

 

 

네번째 고차함수와 일급객체

 

- 고차함수 : 함수가 다른 함수를 매개변수로 받아 로직을 생성할 수 있는 것. (ex. 콜백 함수)

const add = (a,b) => {
	return a+b
	}
    
const result = (a,b,func) => {
	return func(a,b)
	}
    
result(1,2,add) // 3

add라는 함수는 result함수의 인자로 사용되었다. 

 

- 일급 객체의 특징

: 변수나 메소드에 함수를 할당할 수 있다.

: 함수 안에 함수를 매개변수로 담을 수 있다.

: 함수가 함수를 반환할 수 있다.

 

 

 

함수형 프로그래밍의 장점

 

- 함수형 프로그래밍은 높은 수준의 추상화를 제공한다. 상대적으로 코드가 간결해 진다. 

- 함수형 프로그래밍은 데이터의 불변성을 지향한다. 데이터를 변경할 때, 원본 데이터를 직접 변경하지 않고 복사본을 통해 변경한다. 

그래서 동작을 예측하기 쉽다.

- Side effect (함수가 반환 하는 값 이외의, 함수 밖 어플리케이션의 상태 변경) 방지 및 테스트에 용이하다.

 

 

 

참고사이트

- 면접을 위한 CS지식노트 p56 프로그래밍 패러다임 

- https://99geo.tistory.com/m/62

- https://hckcksrl.medium.com/solid-%EC%9B%90%EC%B9%99-182f04d0d2b

- https://velog.io/@younoah/programming-paradigm

- https://velog.io/@hayeon/%EA%B0%9D%EC%A0%9C%EC%A7%80%ED%96%A5-vs-%EB%B0%98%EC%9D%91%ED%98%95-vs-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

- https://jongminfire.dev/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80