개발 패러다임

milknsoda·2023년 6월 12일
0

조각모음

목록 보기
1/1

0. 패러다임의 수명

  1. 문제 인식
  2. 새로운 기술의 등장
  3. 기술의 부흥기
  4. 안정기 또는 정체기

언제나 문제는 프로그램의 덩치가 커질 때 발생한다.
- 자바스크립트에서 객체지향을 하는 게 맞나요? | 요즘IT

1. 순차적 프로그래밍

var num = 1;
num = num + 1;
console.log(num);
  • 순차적 프로그래밍은 말그대로 코드 흐름이 위에서 아래로 진행된다.
  • 이후 일부 코드를 재사용하기 위해 GOTO 문을 통해 코드 흐름을 강제적으로 특정 코드 위치로 이동시키는 방식을 만들어 사용하게 된다.

문제는 프로그램의 규모가 커지자 나타났다.

  • 프로그램의 규모가 커지자 GOTO를 통해서 코드 흐름을 강제적으로 바꾸는 방식이 가독성을 떨어뜨렸고 유지보수를 어렵게 만들었다. → 스파게티 코드가 되어버렸고 도저히 감당할 수 없다.

2. 절차적 프로그래밍

  • 순차적 프로그래밍의 문제를 해결하고자 특정 작업을 수행하는 프로시저라는 개념이 발전한다.
  • 약간의 성능을 포기하고, 가독성과 유지보수의 이점을 얻었다.

프로시저 vs 함수

모두 인자를 받아 특정 작업을 수행하는 프로시저와 함수가 왜 다른 개념인지 이해하기 쉽지 않았다.

  • 프로시저는 리턴 값이 없고, 함수는 리턴 값이 있다 라는 설명을 많이 봤지만 그게 충분한 설명이 되진 않았다.

(1) 프로시저

  • 어떤 작업을 진행하는 순서
  • 프로그램의 흐름을 제어하는 것에 초점을 맞추고 있다.
  • 절차적 프로그래밍에서는 변수들이 전역으로 관리되고 있기 때문에 프로시저 내부에서 값이 변경되면 프로그램 전체에 영향을 준다. → side effect

(2) 함수

  • 어떤 작업을 해야하는가
  • 정해진 기능을 수행하는 것에 초점을 맞추고 있다.
  • 절차적 프로그래밍에서는 등장하지 않는 개념으로, 프로시저 안에서 일어나는 작업의 일부분 혹은 전체에 대한 정의라고 생각된다.
  • 출력이 입력에 의해서만 결정되는 것을 지향하기 때문에 프로시저와 달리 side effect를 고려하고 최소화하는 방향으로 만들어진다.

(+) 순수함수

side effect 없이 온전히 입력값에 의해 출력값이 결정되며, 동일한 입력에 대해 동일한 출력을 내는 함수

  • side effect가 없는 함수
function square(x) {
	return x * x;
}

console.log(square(10)) // 출력: 100
  • square 함수는 출력값이 입력값만으로 결정되며, 그 값이 일정하다.

  • side effect가 있는 함수

var x = [1, 2, 3];
function sliceArr(n) {
	if(x.includes(n)){
		x = x.filter(v => v !==n);
		return true;
	}
	return false;
}

console.log(sliceArr(2)); // true
console.log(x); // [1, 3]
  • sliceArr 함수는 입력값과 전역 변수 x의 값에 영향을 받으면서, 실행 중에 직접적으로 x의 값을 변경한다.
  • 이 함수처럼 외부 변수의 값을 직접적으로 변경하기 때문에 순수함수라고 할 수 없다.

절차적 프로그래밍은 프로그램의 흐름 제어와 재사용 측면에서 이점을 가져다주었다.

하지만 그럼에도 여전히 변수는 전역으로 관리되기 때문에 선언할 때마다 늘어나는 변수를 구분을 위해 prefix 를 붙여 사용하게 되었다.

(3) namespace

  • 프로그램의 규모가 커짐에 따라 prefix만으로 변수를 구분해서 사용하는 것에 한계가 생기며, 파일 또는 모듈 단위로 prefix를 관리하는 namespace라는 개념이 나타났다.

3. 객체지향 프로그래밍

  • namespace를 사용하더라도 여전히 비슷한 형태의 데이터를 만들기 위해서는 개별적인 prefix가 필요하다는 한계가 있었다.
user1_name = '고';
user1_age = 31;

user2_name = '지';
user2_age = 31;
  • user처럼 동일한 형태의 데이터를 효율적으로 관리하기 위해 함께 사용되는 데이터를 묶어 구조체를 만들어 관리하게 되었다.

(1) 클래스

구조체는 관리하는 데이터가 비슷하다보니 자주 사용되는 함수들이 생겼고, 데이터와 함수를 함께 관리하려는 시도가 생겨났다. → 클래스

class User {
  name ='이름'
  age = 1
  hello() {
     console.log('안녕! ' + this.name)
  }
}
  • 클래스가 생겨남에 따라 데이터와 그 데이터를 사용하는 함수가 묶여 프로그램을 구성하는 작은 단위로 기능했다.
  • 클래스를 기준으로 만들어진 실제 데이터는 현실의 개별 개체와 닮았다해서 오브젝트(object) 라고 부르게 되었다.

(2) 디자인 패턴

객체지향 프로그래밍의 패러다임이 퍼져나가 널리 사용될수록 개발자들은 비슷한 문제를 겪게 되었다. 디자인 패턴은 이러한 일반적인 문제들에 대한 일반적인 해결책을 정리한 것이라고 할 수 있다.

디자인 패턴들

이디엄 : 가장 기본적인 하위 설계 패턴

아키텍처 패턴 : 상위 설계 패턴, 가장 보편적

  1. 생성 패턴
    • 기존 코드의 재활용과 유연성을 증가시키는 객체 생성 메커니즘 → 재사용성 관점(?)
  2. 구조 패턴
    • 구조를 유연하고 효율적으로 유지하면서 객체와 클래스를 더 큰 구조로 조합하는 방법 → 확장성 관점(?)
  3. 행동 패턴
    • 객체 간의 효과적인 의사소통과 책임 할당을 처리 → (?)

생성 패턴

  1. 팩토리 메서드

    문제 : 특정 객체에 대해서만 고려된 구조의 프로그램에 다른 유형의 객체가 추가되어야 하는 상황에서 기존 코드를 재사용하면서 프로그램을 확장할 방법이 필요해졌다.

    생성자를 직접 사용하지 않고, 팩토리 메서드의 결과로 객체를 반환한다.

    • 크리에이터 : 제품 인터페이스에 맞는 객체를 반환하는 팩토리 메서드
    • 구상 크리에이터 : concrete creator, 팩토리 메서드에 오버라이드해서 다른 유형의 객체를 만들 수 있도록 한다. 중요한 것은 제품 인터페이스의 공유다.
    • 제품 인터페이스 : 제품 객체가 가지는 공통 특성
    • 구상 제품 : 팩토리 메서드를 통해 만들어진 객체
    • 클라이언트 코드 : 팩토리 메서드를 사용하는 코드
    • 생성자 코드를 객체를 사용하는 코드에서 분리함으로써 다른 유형의 객체가 필요하게 되었을 때, 팩토리 메서드만 변경하여 코드를 유지할 수 있다.
    • 단, 팩토리 메서드를 사용하기 위해서는 같은 인터페이스를 따르는 객체여야한다.
    • 구상 크리에이터를 통해 동일한 인터페이스에 대해 재정의를 하여 같은 기능이지만 다르게 동작하도록 할 수 있다.
    class User { // 추상화되어 있는 인터페이스로 보자.
    	name = ''
      age = 0
      callName() {
    		console.log('이름 : ' + this.name + ', 나이 : ' + this.age)
    	}
    }
    
    function creator(name, age) {
    	let newUser = new User();
      newUser.name = name;
      newUser.age = age;
      newUser.callName = function() {
        console.log('안녕, 내 이름은 ' + this.name)
      }
    	return newUser;
    }
    
    function concreteCreator(name, age) {
    	let newConcreteUser = creator(name, age);
    	newConcreteUser.callName = function() {
        console.log('난 ' + this.name + '이고, ' + this.age + '살이야.')
      }
    	return newConcreteUser;
    }
    
    let user1 = creator('고길동', 30);
    let user2 = concreteCreator('둘리', 500);
    
    user1.callName(); // 출력 : 안녕, 내 이름은 고길동
    user2.callName(); // 출력 : 난 둘리이고, 500살이야.
    • user1user2는 모두 nameage, callName을 가지고 있지만 실제로 callName은 다르게 동작한다.
    • 코드 상에서 creatorconcreteCreator를 이용해서 같은 속성을 지녔지만 다르게 동작하는 객체를 만들 수 있게 됨으로써, 구상 크리에이터 외에는 별도 작업 없이 객체 유형의 추가를 통한 프로그램의 확장이 가능해졌다.
    • 팩토리 메서드의 결과는 항상 새로운 인스턴스가 아니어도 된다
      • 정해진 범위 내에서 필요한 객체를 찾아 반환하는 경우에도 적용 가능하다.
  2. 추상 팩토리

    서로 다른 인터페이스의 객체들이 변형에 따라 세트로 동작해야하는 경우에는 추상 팩토리 패턴을 적용할 수 있다.

    • 예시: 컵받침이라는 제품이 있을 때, 컵과 컵받침은 각각 모던클래식 디자인을 가진다.
    • 이때 추상 팩토리 패턴을 글로 표현하자면,
      1. (모던 | 클래식) 디자인의 컵과 컵받침 세트를 구매한다. → 클라이언트 코드
      2. (모던 | 클래식) 디자인 컵과 컵받침을 함께 포장한다. → 추상 팩토리
      3. (모던 | 클래식) 디자인 컵을 만든다. → 팩토리 메서드 (1)
      4. (모던 | 클래식) 디자인 컵받침을 만든다. → 팩토리 메서드 (2)
    • 코드로 구현해보면 이런 형태가 될 것이다.
    class Cup {
    	design = ''
    	pack() {
    		console.log(this.design + '컵을 포장합니다.')
    	}
    }
    
    class Coaster {
    	design = ''
    	pack() {
    		console.log(this.design + '컵받침을 포장합니다.')
    	}
    }
    
    function createModernCup() {
    	let cup = new Cup();
    	cup.design = 'modern';
    	return cup;
    }
    
    function createClassicCup() {
    	let cup = new Cup();
    	cup.design = 'classic';
    	return cup;
    }
    
    function createModernCoaster() {
    	let coaster = new Coaster();
    	coaster.design = 'modern';
    	return coaster;
    }
    
    function createClassicCoaster() {
    	let coaster = new Coaster();
    	coaster.design = 'classic';
    	return coaster;
    }
    
    class AbstractFactory {
    	cup = {}
    	coaster = {}
    	packageProduct() {
    		this.cup.pack();
    		this.coaster.pack();
    	}
    }
    
    function packageModern() {
    	let fac = new AbstractFactory();
    	fac.cup = createModernCup()
    	fac.coaster = createModernCoaster();
    	return fac;
    }
    
    function packageClassic() {
    	let fac = new AbstractFactory();
    	fac.cup = createClassicCup()
    	fac.coaster = createClassicCoaster();
    	return fac; 
    }
    
    let product;
    let choosenDesign = window.prompt('디자인을 선택해서 입력해주세요. (modern | classic)'); 
    if(choosenDesign == 'modern'){
    	product = packageModern();
    }else if(choosenDesign == 'classic'){
    	product = packageClassic();
    }else{
    	console.log('디자인을 선택해주세요');
    }
    
    if(product) product.packageProduct();

0개의 댓글