선언형, 명령형 프로그래밍

Min·2021년 1월 6일
4

JavaScript

목록 보기
2/18
post-thumbnail

프로그래밍의 분류

출처: 더 알아보기

1. 선언형(Declearative) 프로그래밍

무엇인가를 작업하기 위한 방법을 정의한다.
(뚜껑을 연다 -> 물을 붓는다 -> 면이 익을 때까지 기다린다.)

1) 함수형(Functional) 프로그래밍

변경 가능한 상태를 불변상태(Immutab)로 만들어 SideEffect를 없앤다.

  • f(x) = y. 함수f()에 x 를 입력하면 항상 y라는 결과값이 나온다.
  • 함수 안에서 상태를 관리하고 상태에 따라서 결과값이 달라지면 안된다.
  • 변수 보다는 상수를 사용해 SideEffect를 차단한다.

모든 것은 Object(객체)이다.

  • 함수형 언어에서는 class외에 함수 또한 객체이다. = value(값)처럼 쓰일 수 있다.
  1. 변수나 데이타에 할당 할 수 있다.
  2. 객체의 인자로 넘길 수 있다.
  3. 객체의 리턴값으로 리턴 할수 있다.
    = 함수형 프로그래밍 언어에서는 함수가 1급 객체이다! + 함수를 받아서 함수를 반환할 수 있다.

2) 고차함수(Higher Order Functions )

  • 다른 함수를 이용해서 완전히 새로운 함수를 조립 하는 방법으로 프로그램을 만들 수 있다.
    - 콜백 함수, 프로미스, 모나드 등을 사용하여 액션, 효과 또는 비동기 흐름을 추상화하거나 분리시킨다.
    - 다양한 데이터 타입에 대해 동작할 수 있는 유틸리티를 만든다.
    - 합성 함수나 재사용의 목적으로 커링 함수를 만들거나 인수를 함수에 부분적으로 적용한다.
    - 함수 목록을 가져오고, 입력 함수의 합성을 반환한다.

코드를 간결하게 하고 가독성을 높여 구현할 로직에 집중 시킨다.

  • boilerplate(일을 위한 일들)를 제거하여 실제 구현할 로직에만 집중 가능하고,
    내부에 직접적인 함수 호출을 통해 가독성을 높일 수 있다.

동시성 작업을 보다 쉽게 안전하게 구현한다.

  • 불변 상태의 값을 사용해, 여러 스레드에서 접근하더라도 SideEffect를 발생시키지 않는다.
  • Lock, UnLock 같은 보호장치도 필요 없다.
  • 데이터 변경이 불가능하기 때문에 기존 데이터의 복사본을 만들어 주는 method가 필요하다.
    ex) JavaScript의 Array.map, Array.reduce
const schools = [
  "Yorktown"
  "Washington",
  "Wakefield"
];

// Array.map (맵핑): 모든 원소에 'High School' 문자열 추가된 새로운 배열 만들기
const highSchools = (schools.map(school => `${school} High School`));
console.log(highSchools.join("\n"));

// Array.reduce (축약): 배열에서 최대 값 찾기 (배열을 하나의 수로 변환)
const result = [21, 18, 42, 40, 64, 63, 24].reduce((max, num) => num > max ? num : max, 0);
console.log(result); // 64

// Array.join (합치기): 콤마(,)로 각 학교를 구분한 문자열 얻기
console.log(schools.join(",")); // "Yorktown", "Washington", "Wakefield"

// Array.filter (걸러내기): 'W'로 시작하는 학교만 있는 새로운 배열 만들기
// 원소를 제거하는 경우 Array.pop, Array.slice가 아닌 순수함수인 filter를 사용할 것
console.log(schools.filter(school => school[0] === "W")); // ["Washington", "Wakefield"]

순수함수(Pure Function)

  • 동일한 argument(인자) 입력에 대해 항상 동일한 return(출력)값을 반환 한다.
  • side-effects가 없다 (프로그래머가 바꾸고자 하는 변수 외에는 바뀌어서는 안된다.)
  • 함수와 데이터를 중점으로 생각한다.
// 순수하지 않습니다 (반환값 없이 부가 효과를 이용하고 있습니다)
function addTaco(array) {
   array.push("taco");
}

// 순수하지 않습니다 (인자 대신 공유 변수를 이용하고 있습니다)
function addTaco() {
   return [...globalArray, "taco"];
}

// 순수합니다
function addTaco(array) {
   return [...array, "taco"];
}
  • 순수 함수는 프로그래머가 모든 것을 예측하고 통제할 수 있어야 한다!
    (예측이 가능하니, 디버그가 쉬우며 테스트하는 것은 더욱 쉽다. )
  • 프로그램의 의미를 변경하지 않고 결과 값으로 함수 호출을 대체할 수 있는 referentially transparent(참조 투명성) 덕분에, 함수 결과를 캐싱하여 반복적으로 사용 할 수 있는 memoization(메모이제이션) 최적화가 가능하다.

3) 합성 함수(Function composition)

  • 새로운 함수를 만들어거나 계산하기 위해 둘 이상의 함수를 조합하는 과정을 말한다. 함수형 프로그램은 여러 작은 순수 함수들로 이루어져있기 때문에 이 함수들을 연쇄적으로 또는 병렬로 호출해서 더 큰 함수를 만드는 과정으로 전체 프로그램을 구축해야 한다.
  • 메서드 체이닝 방식의 합성함수
const sum = (a, b) => a + b
const square = x => x * x
const addTen = x => x + 10

const computeNumbers = addTen(square(sum(3, 5))) // 74

// compose는 함수를 연쇄적으로 호출하면서 반환값을 전달한다 
const compose = (...fns) =>
  fns.reduce((prevFn, nextFn) =>
    (...args) => nextFn(prevFn(...args)),
    value => value
  );

// compose의 사용
const compute = compose(
  addTen,
  square,
  sum
)
compute(3, 5) // 74

2. 명령형(Imperative) 프로그래밍

무엇인가를 작업하기 위해 어떻게 진행할 것인지를 나열한다.
ex) 컵라면을 끓인다.

출처: 더 알아보기

1) 절차지향(Procedural) 프로그래밍

  • 물이 위에서 아래로 흐르듯이 순차적으로 처리가 이루어지며, 프로그램 전체가 유기적으로 연결된다.
  • 데이터를 중심으로 프로그램 구현
  • 장점 : 컴퓨터의 처리구조와 유사해 실행속도가 빠르다.
  • 단점
    • 실행순서가 정해져 있어, 코드의 순서가 바뀌면 동일한 결과를 보장하기 어렵다.
    • 유지보수가 어렵다.
    • 디버깅이 어렵다.
  • 대표적 언어 : C언어

2) 객체지향(Object Oriented) 프로그래밍

객체 지향 프로그래밍(OOP)은 컴퓨터 프로그래밍의 패러다임의 하나이다.
OOP는 데이터와 이를 처리하는 루틴들을 하나의 “독립된 객체”로 바라보는 시선이다.

기존에 만들었던 내용을 쉽게 상속, 다형성 등을 이용해서 재사용이 가능하므로 큰 프로젝트에서 많이 사용된다. 또한 생성자, 소멸자, 접근제어[Public, Private, Protected] 와 같은 내용은 개발자의 실수로 인한 취약점을 줄여준다.

  • 실제 세계를 추상화하여 객체 공통의 Property와 method를 추출한다.
    ex) 휴대전화의 공통부분 : 통화한다(0), 사진을 찍는다(0) / 삼성에서 만들었다(x) 폴더블이다(x)
  • 데이터와 절차를 하나의 덩어리로 묶어서 생각한다. (위의 그림 참조)
  • 기능을 중심으로 프로그램 구현

특징

① 추상화(Abstraction)
프로그램을 만듬에 있어서 간단한 예를 생각하려 한다. 스타크래프트에서 유닛을 만든다고 생각을 한다. 그러면 기본적으로 들어갈 정보를 생각할 수 있을 것이다. 그리고 구체화시켜 나가는 것이다. 예를 들어 HP, MP, NAME 과 같은 데이터들과 이동, 공격과 같은 함수들이 존재할 것이다.
이러한 추상화를 통해서 Class를 만들어 놓고 저글링이나 질럿, 마린과 같은 상세한 부분들을 채워 나가는 것이다.

// 가상 클래스
// 기본적은 몬스터들의 틀[class]
// 추상 클래스
class Unit {

// 태어나는 위치
private : 
       int x;
       int y;

// 기본 속성
protected:
       unsigned char HP;
       unsigned char MP;
       char name[20]

public:
		// 생성자
        Unit( );
        // 태어남
        virtual void born( ) = 0;
        // 소리남 응애응애
        virtual void sound( ) = 0;
};

② 캡슐화(Encapsulation)

  • 은닉성: 묶은 내용 중, 외부에서 접근하지 못하는 Property와 method를 만들 수 있다.
    외부와의 상호작용은 method를 통해 이루어진다.

  • 데이터 알고리즘(데이터를 다루는 방식)을 하나의 묶음으로 정리한다.
    (변수와 함수를 Class로 묶는다.)

객체의 내용 중 숨기고 싶은 부분은 외부에서 접근할 수 없다.
단지 해당 객체 내의 함수에 의해서만 접근 가능하도록 하는 것이다.
이를 통해서 정보 은닉이 가능해진다. 앞서 말한 접근제어[public, private, protected]와 같은 내용에 의해서 구현되어진다. 접근 권한을 통해 제공되며 원하지 않는[실수] 외부의 접근에 대해 내부의 데이터, 함수를 보호하는 작용이다. 이렇게 함으로써 이들 부분이 프로그램의 다른 부분들에 영향을 제한한다. 예를 들어 설명하면, 각 Unit 마다 고유한 값이 존재한다. 하지만 같은 변수명을 사용함에 있어서 실수로 고유한 값을 수정해버리면 문제가 발생한다

// 유닛에서 저글링 만들기
class zergling : public Unit {

// speed 추가
private:
    unsigned char speed;

public:
     zergling( );
     virtual void move( );
     virtual void sound( );
     void up_speed( );
     void up_speed(int var);
};

// private 영역 접근 가능한 클래스 내 함수
void zergling::up_speed( ) {
    speed += 10;
}

③ 상속(Inheritance)

  • 부모 class의 Property와 method를 자식 class에서 사용할 수 있어 코드 재사용이 가능하고, 중복코드를 제거할 수 있으며, 물려 받은 Property와 method와 더불어 필요한 기능을 추가해 확장할 수도 있다.

Class의 멤버[데이터]와 함수를 다른 Class에 물려주거나, 물려 받는 것을 말한다.
이를 통해서

  • 코드의 재사용을 증대시킬 수 있다. 같은 기능을 또 구현할 필요가 없다.
  • 좀 더 폭넓게 사용가능하다. 상속받은 함수를 추가적으로 데이터와 함수 내용을 변경하는 것이 가능하다.
    같은 기능을 가진 함수는 그대로 사용할 수 있고, 같은 기능의 함수를 다른 이름으로 만들 필요도 없어진다.
// 유닛에서 저글링 만들기
class zergling : public Unit {
// speed 추가
private:
    unsigned char speed;

public:
    zergling( );
    virtual void move( );
    virtual void sound( );
    void up_speed( );
    void up_speed(int var);
};
// Unit을 상속받아서 zergling 생성

④ 다형성(Polymorphism)

  • 하나의 class나 method가 다양한 방식으로 동작이 가능 한 것을 의미한다.

출처: 더 알아보기

  • Overriding (오버라이딩)
    필요에 따라서, 하위 class에서 상속받은 상위 class의 method를 그대로 사용하지 않고 내용을 재정의 하여 사용하는 것.

  • Overloading (오버로딩)
    같은 method 이름을 사용하지만, 인자의 정보가 다르면 넘겨받은 정보에 맞는 method가 호출되는 것. (다양한 입력에 대해서 처리 할 수 있다.)
    객체 지향 언어에서 다형성이란 하나의 클래스나 메서드가 다양한 방식으로 동작이 가능 한 것을 의미합니다. 이러하 다형성에는 오버로딩과 오버라이딩이 존재합니다.

  • 장점

    • 코드의 재활용성이 높다
    • 코딩이 절차지향보다 간편하다
    • 디버깅이 쉽다
  • 단점

    • 처리속도가 절차지향에 비해 느리다
    • 설계에 많은 시간이 소요된다.
  • 대표적 언어: C++, JAVA, Python

다양한 형태로 표현이 가능하다는 의미이다.
예를 들어 설명하면 사람의 말투와 같은 느낌인 것 같다.
사람의 말투에 따라서 야. 야!, 야~ 와 같이 다른 의미를 전달한다.
야. 는 정색하는 말투. 야! 는 호통하는 말투 야~ 는 애교를 부리는 말투 등으로 같은 [야] 이지만 다른 의미를 전달하는 느낌이다.
여기서는 Overloading 과 Overriding 으로 표현이 가능하다.

  • Overload는 초과 적재라는 의미, Overriding은 무효화하다 라는 의미이다. Overload는 하나의 함수에 여러 개의 기능을 부여하는 것.
  • Override는 기존의 기능을 고려하지 않고 새로운 기능을 부여하는 것.

Dynamic Binding이라는 개념도 해당 다형성에서 나온다
함수가 호출되기 전까지는 up_speed( )함수 중 어떠한 것과 연결되는지 모른다.
하지만 up_speed( )가 호출되면 속도가 +10이 되고, up_speed(30)하면 +30으로 연결될 것이다. 이러한 내용을 동적 바인딩이라고 한다.

1. 오버로딩[Overloading]
같은 기능의 함수에 같은 이름을 사용할 수 있어서 가독성 증가
하나의 함수에 여러 매개변수 가능
조건 : 함수명은 동일해야 함.
넘겨주는 인자[매개변수]는 달라야 함.
리턴 타입은 상관 없음.

2. 오버라이딩[Overriding]
상속으로 받은 함수를 그대로 사용하지 않고 새로 만들어서 사용.
코드의 재사용성을 역시 증가시킨다.
조건 : 함수명은 동일해야 함.
넘겨주는 인자 같아야 함.
리턴 타입 같아야 함.

// 오버로딩[Overloading]
void zergling::up_speed( ) {
       speed += 10;
}
void zergling::up_speed(int var) {
       speed += var;
}

// 오버라이딩[Overriding]
void zergling::sound( ) {
       print(“북적 북적\n”);
}

void Unit::sound( ) {
       print(“꿈틀 꿈틀.. 아무런 소리가 나지 않는다.\n”);
}

참고 칼럼

선언형(declarative) vs 명령형(imperative)
절차지향 VS 객체지향
[객체 지향 언어의 이해] 객체 지향의 4대 특성 04 - 다형성과 캡슐화
JavaScript 함수형 프로그래밍 3단계로 설명하기
함수형 프로그래밍(Functional Programming)
함수형 프로그래밍이란?
함수형 프로그래밍 요약
고차 함수
객체 지향[oop] 특징 4가지

profile
slowly but surely

0개의 댓글