본문 바로가기
Computer Science/Others

프로그래밍 언어와 디자인 패턴

by Luinesse 2024. 3. 25.
반응형

프로그래밍 언어는 크게 분류했을 때 다음 3가지로 나뉩니다.

 

  • 절차지향 언어
  • 객체지향 언어
  • 함수형 언어

절차지향 패러다임을 따르는 절차지향 언어는 가장 기본적인 방식으로 기계어, 어셈블리어가 속하며, 고급언어 중에서는 대표적으로 C언어가 있습니다. 절차지향 언어는 Turing Computable한 명제에 대해서는 순차, 분기, 반복, 참조 4가지로 구현이 가능하다는 점이 핵심입니다.

 

순차 방식은 프로그램에 실행 포인터가 존재하여 각 포인터에 있는 코드를 한줄씩 순서대로 실행시킵니다.

분기 방식은 프로그램의 실행 포인터가 임의의 상황에 다다랐을 때, 일부 내용을 넘기는 방식으로 다양한 상태를 만들고 이를 통해 다양한 프로그램을 생성합니다.

반복 방식은 프로그램의 실행 포인터를 조작하여 이미 실행한 내용으로 이동하게 하여, 적은 코드를 작성해도 같은 실행효과를 가지는 것을 의미합니다.

참조 방식은 각종 데이터의 값만 사용하는 것이 아닌, 주소 위치까지 조작하여 이를 통해 다양한 프로그램을 생성하는것을 의미합니다.

 

객체지향 패러다임을 따르는 객채지향 언어는 모든 것을 객체 단위로 두어 객체를 판단하고, 객체와의 상호작용을 명시하는 방식의 언어입니다. 객체는 데이터와 메소드로 구성되고, 프로그램은 데이터의 변경을 메소드로 변경하고 객체들간의 특수한 관계를 통해 프로그래밍 됩니다. 객체지향 언어는 강한 응집력, 약한 결합력의 특징을 가집니다. 그결과 강한 응집력으로 객체를 구성하고, 객체 내에서는 약한 결합력으로 인해 높은 자유도를 가집니다. 객체지향 언어는 추상화, 상속, 다형성, 캡슐화의 4가지 특징으로 구조적이고 유동적인 프로그래밍을 도와줍니다. 현대에 들어서는 대부분이 객체지향 언어로 작성된 프로그램이 많고, 그에 따라 복잡한 규모의 프로그램이 작성되어 가독성과 편한 프로그래밍을 위해 여러가지 디자인 패턴들이 구상됐습니다.

 

객체지향의 4가지 특징 중 먼저 캡슐화는 객체 내부(캡슐 내부)에서는 자유롭게 사용되지만, 외부(캡슐 외부)에서는 접근할 수 없고 안에서 나갈 수 없게 하는 역할을 합니다. 외부에서 내부의 동작구조를 알 수 없게 끔 보호하는 것을 데이터 보호라고 하며, 내부의 값을 외부에서 임의로 사용하려 하는 경우, 최소한의 필요부분만 노출하는 것을 데이터 은닉이라고 합니다. 객체지향 언어에서 캡슐화 이루기 위해서 접근제어자와 Getter, Setter를 사용합니다.

 

먼저 접근 제어자는 private, protected, public, default로 이루어져 있는데, 클래스의 인스턴스 혹은 메소드를 외부에서 어느정도 권한으로 접근할 수 있는지를 정할 수 있습니다. private는 내부에서만 사용가능하고, protected는 동일 패키지와 다른 패키지의 하위 클래스에서 사용가능하고, public은 제한이 없으며 default는 동일 패키지 내에서만 사용가능합니다.

 

다음으로 Getter, Setter는 인스턴스의 접근 및 변경을 직접적으로 하는것을 막고, Getter와 Setter 메소드로만 접근 및 변경을 허용합니다. 메소드로 작성하여 데이터 은닉을 구성합니다.

 

다음으로 상속입니다. 상속은 기존의 클래스를 재활용하는데, 두 클래스는 상하관계를 가진채로 사용하는 것입니다. 보편적으로 상위 클래스와 하위클래스는 1:N 관계를 띄는데 다중 상속을 하게될 경우 Diamond Problem이 발생하는 관계로 다중상속을 지원하지 않습니다. (예외적으로 C++의 경우 다중 상속을 지원합니다.) 상위 클래스 집합은 모든 하위 클래스를 포함하며, 하위 클래스를 변경할 수 있습니다. 하지만, 모든 하위 클래스는 상위 클래스를 받거나, 변경할 수 없습니다. 다만 상위 클래스의 메소드를 오버라이딩하여 사용할 수 있기 때문에, 오버라이딩된 상위 메소드는 해당 하위 클래스에서만 상위 메소드를 수정하여 사용할 수 있습니다.

 

다음으로 다형성입니다. 다형성은 하나의 인스턴스, 메소드가 여러 형태로 사용될 수 있도록 지정하는 것을 말합니다. 다형성은 크게 다음 4가지로 구성됩니다.

 

  • 하위타입 다형성
  • 매개변수 다형성
  • 임시 다형성
  • 강제 다형성

하위타입 다형성은 상위타입의 메소드를 메소드 오버라이딩을 하여 하위타입은 다형성을 가지는것을 의미합니다.

매개변수 다형성은 타입을 매개변수로 받아 새로운 타입을 리턴하는 타입 컨스트럭터 기능을 수행하여 다형성을 갖는것을 의미합니다. 대표적으로 C++의 템플릿이나 Java의 제네릭이 여기에 속합니다.

임시다형성은 메소드를 각기 다른 입력이나 출력으로 복수 정의를 하여 사용할 때마다 매번 다른 입력의 같은 함수를 조건에 맞게 변형해주는 것을 말하는데, 보통 메소드 오버로딩으로 많이 사용합니다. 연산자 오버로딩 또한 임시 다형성에 속합니다.

마지막으로 강제 다형성은 강제로 상위 범위의 타입으로 강제 변환하거나, 사용자가 명시적으로 변환을 해서 계산과정의 일관성을 유지합니다. 대표적으로 묵시적 타입변환과 명시적 타입변환이 있는데, 묵시적 타입변환은 float 타입의 변수에 int를 입력하여도 결과는 4.00과 같이 실수형태로 묵시적 변환이 되는 예시가 있고, 명시적 타입 변환은 'a'를 입력했지만, 이를 정수로 타입캐스팅을 통해 아스키코드 값을 얻을 수 있습니다.

 

마지막 특징은 추상화는 객체지향에서 공통 속성과 기능을 추출하여 추상 클래스로 만들어 따로 관리할 수 있게끔 합니다. 클래스는 일반 클래스와 추상 클래스, 인터페이스(언어에 따라 지원X)로 나뉘어 지고, 각각의 차이점이 존재합니다. 먼저 추상 클래스는 생성자의 생성이 가능하고, 클래스 변수를 생성할 수 있으나, 상수로 생성해야합니다. 즉, 값을 가지고 있어야하며, 구상 메소드의 생성이 가능합니다. 뿐만아니라 추상 메소드로도 생성이 가능합니다. 인터페이스의 경우는 추상 클래스와 같이 상수로 변수를 선언합니다. 동일하게 값을 가져야하므로 상수로 생성해야합니다. 하지만, 인터페이스에서는 값을 가진 변수는 묵시적으로 상수로 선언이 됩니다. (int a = 10; 이라고 작성해도 알아서 static final int a = 10; 으로 변환) 그리고 구상 메소드의 생성이 불가능하고, 추상 메소드의 생성만 가능합니다. 일반 클래스의 경우 추상 클래스 a를 상속받고, 추상 클래스의 추상 메소드들의 정의가 포함되어야 합니다. (구상 메소드로 오버라이딩) Java의 경우 abstract로 추상 클래스를 생성하며, 일반 클래스에서 상속받을 땐 extends로 상속 받습니다. 인터페이스의 경우, implements로 상속받습니다. 여기서 인터페이스는 다중 상속이 가능합니다. (C++의 경우 virtual로 상속. 다중 상속 X)

 

객체지향 프로그래밍 설계를 도와주고, 가독성을 높여주는 도구로 디자인 패턴을 언급했었는데, 그 이유로는 실무에서 코드 작성은 개인이 모든 내용을 다하고 디버깅하며 리팩토링하는것이 아니기 때문에 일관성을 유지하기 위해 디자인 패턴에 맞추어 작성합니다. 최근에는 너무나도 많은 디자인 패턴들이 존재하는데 이 중 대표적인 몇가지만 간단하게 서술하겠습니다.

 

먼저, Iterator Pattern입니다. Iterator는 원소를 하나씩 실행시키는 인터페이스로, 반복자 패턴이라고 불립니다. 예를 들어 도서관이라는 객체를 구성하게 됐을 때, 도서관의 책장에 꽂혀있는 책의 이름을 가져오고 싶을 때, 도서관이라는 객체에서 Iterator 인터페이스를 통해 각 원소(클래스)의 이름변수를 출력하는 메소드를 실행하는 형식으로 코드를 작성합니다.

 

다음으로 Singleton Pattern입니다. 싱글톤 패턴은 인스턴스가 하나만 존재하는 것을 보장하는 패턴으로, 생성자를 private로 생성하는게 특징입니다. 이를 통해 외부에서 new를 통해 인스턴스를 생성할 수 없고, getInstance와 같은 메소드를 생성하여 인스턴스로 생성자를 호출하여 생성된 인스턴스를 리턴해주는 형태로 인스턴스는 무조건 1개만 생성되고 호출할 때 마다 동일한 인스턴스를 반환합니다. 싱글톤 패턴을 사용함으로써 메모리 관리에서 이점을 얻을 수 있고(인스턴스는 1개만 존재하므로), 싱글톤이 동일한 인스턴스를 계속해서 반환하는 특징을 통해 데이터 공유에서 이점을 얻을 수 있습니다.

 

다음으로 Observer Pattern입니다. 옵저버 패턴은 관찰 대상의 상태가 변화할 때까지 지켜보고있다가 변화가 일어나면 즉시 관찰자에게 알려주는 패턴으로 웹개발에서 이벤트리스너가 대표적인 옵저버 패턴 중 하나입니다. (이벤트 리스너를 클릭을 감지하게 하면, 클릭을 감지하게 되면 상태 변화를 리턴하므로 그걸 이용하여 코드의 작성)

 

다음으로 Strategy Pattern입니다. 전략 패턴은 프로그램이 진행됨에 있어서 다양한 전략들을 사용할 수 있는데, 그러한 경우의 수들을 각 상황에 맞게 사용할 수 있게끔 하기 위해 인터페이스나 추상 클래스 등을 통해 어떤 경우의 수에 사용할 전략들을 정의해두고 상속을 통해 조립식 코딩을 하는 방식입니다. 이러한 방식을 위임 방식이라고 하며, 이 방식은 약한 결합력을 위해 사용하기 때문에 좀 더 유연하게 객체지향 방식의 코딩이 가능합니다. (즉, 메인 메소드에 전략 추상 메소드들을 통해 의존성을 주입하므로 코드의 수정은 전략 메소드들의 수정만 하면 그대로 수행이 되므로 모든 것을 객체로 두고 작성하는 객체지향 방식의 코딩에 적합.) 이러한 방식은 스프링 프레임워크에서도 똑같이 적용됩니다. 부가적으로 여기서 익명 내부 클래스 (Anonymous Inner Class)를 함께 사용하여 전략 패턴을 구상하면 템플릿 콜백 패턴이 됩니다.

 

다음으로 Decorator Pattern입니다. 데코레이터 패턴은 기능을 계속해서 붙여서 추가 가능한 패턴으로, 실행 중에 동적으로 기능 확장 또한 가능한 패턴입니다. 투과적인 인터페이스를 구현하거나 내용물이 변경되지 않으면서 기능을 추가하고 싶은 경우 해당 패턴을 채택합니다. 예를 들어, 커피라는 객체를 구현하고, 커피에는 우유, 얼음 등을 추가하는데 이를 각각 클래스로 구현하고, 베이스가 되는 클래스의 인스턴스를 통해 우유 클래스, 얼음 클래스에서 값을 추가해서 리턴하는 방식입니다.

 

다음으로 Builder Pattern입니다. 빌더 패턴은 데코레이터 패턴과 유사하지만(필자 본인의 생각) 빌더 패턴은 생성자로 빌를 만들고 거기에 필요한 속성들을 입력하는 메소드들을 통해 함수 합성처럼 붙여서 정의하는 방식입니다. 그리고 마지막에 빌드 메소드를 통해 사용자가 원하는 객체를 생성하여 리턴합니다. 의존성 주입 패턴은 빌더 패턴의 일종입니다.

 

마지막으로 Visitor Pattern입니다. 비지터 패턴은 데이터 구조 및 생성과 데이터 처리 과정을 분리하여, 코드를 직관적으로 관리하는데 이는 추후 서술할 SOLID 개발 원칙 중 개방 폐쇄 원칙을 정직하게 따르는 패턴입니다. 확장에 대해서 열려있고 수정에 대해서 닫혀있는 패턴으로, 방문자를 나타내는 클래스와 방문자를 accept 메소드를 통해 받아들이는 방식으로, 새로운 방문자가 생길 때는 새로운 방문자를 나타내는 클래스를 생성하고 똑같이 accept 메소드를 통해 받아들이므로 확장에서는 문제가 없으며 이미 생성된 내용을 수정하면, 그와 연계된 메소드와 매개변수에 오류가 생기므로 수정에는 닫혀있는 모습을 보입니다. 예를 들어 마트에서 물건을 담는 행위를 한다고 가정할 때, 방문자의 역할을 할 Element 클래스가 있다. 이는 구상 메소드로 accept를 가진다. 그리고, 담긴 물건들을 카트에 담게되므로, 똑같이 Element 클래스를 상속받고 반복문을 돌며 Element를 상속받은 각 물건(product 클래스)의 accept 메소드를 실행한다. 그리고 Visitor 인터페이스는 처리를 위한 메소드를 메소드 오버로딩을 통해 만들어두고, 이를 사용자에 상속시켜 구상 메소드를 만들고, 메인에서는 물건을 가진 사용자와 카트만 있으면 accept를 통해 연쇄적으로 처리가 가능합니다. 필자는 이해하기에 조금 어려워서 말이 길었지만, 단순하게 각 처리를 담당하는 클래스가 있고, 그 클래스를 방문하면서 사용자가 원하는 처리를 수행합니다. 때문에 새로운 처리는 새로운 클래스를 만들어서 똑같이 생성하면 되므로 확장에 대해서 열려있습니다.

 

객체지향 스타일의 개발 원칙으로는 앞서 잠깐 언급했던 SOLID 개발 원칙이 대표적입니다. SOLID 개발 원칙은 다음 5가지로 나열됩니다.

 

  • 단일 책임 원칙
  • 개방 폐쇄 원칙
  • 리스코프 치환 원칙
  • 인터페이스 분리 원칙
  • 의존 관계 역전 원칙

먼저 단일 책임 원칙 (Single Responsibility Principle)은 모든 클래스는 각자 하나의 책임을 지니며, 하나의 책임은 관련된 모든것을 책임지게 하고, 관련되지 않은 것은 어떠한 것도 책임지지 않도록 하는 원칙입니다. 이 원칙을 지킴으로써 얻는 이점은 리팩토링을 하게 될때 수정을 하게 된 메소드가 단일 책임 원칙을 따른다면, 수정으로 인하여 연쇄적으로 수정해야하는 작업이 없습니다. 만일 따르지 않게 된다면, 메소드1의 수행 중 메소드2의 값을 어느 정도 의존하게 되어 메소드1을 수정하면 메소드2도 수정해야하므로, 코드 관리에 있어서 불리합니다.

 

두번째로 개방 폐쇄 원칙 (Open Close Principle)입니다. 앞서 비지터 패턴에서 서술했던 내용으로, 확장에 열려있고 수정에 폐쇄되는 원칙입니다. 비지터 패턴에서 보였듯, 새로운 처리를 위해서는 새로운 클래스를 Element 인터페이스를 상속받아 확장합니다. 따라서, 정의된 인터페이스에 의존하므로, 수정에 닫혀있으나, 해당 인터페이스로 상속받아서 계속해서 확장하므로 결합도를 줄이고 응집력을 높이는 효과를 보입니다.

 

세번째로 리스코프 치환 원칙 (Liskov Substitution Principle)입니다. 리스코프 치환 원칙은 자료형 S가 자료형 T의 서브 타입이라면, (T >= S) 프로그램의 변경 없이도 자료형 T는 S로 변환할 수 있어야합니다. 즉, 서브 타입은 언제나 기반 타입이 될 수 있어야 한다는 원칙인데, 대표적인 예시로 Java의 콜렉션이 있습니다. 콜렉션을 사용하지 않고 지금 당장 HashMap 자료형이 필요하여 사용하다가 LinkedList가 필요한 순간이 오면, 많은 부분을 수정해야합니다. 이러한 문제는 리스코프 치환 원칙을 적용하지 않아서 일어나는 사례로 이를 해결하기 위해 HashMap 자료형으로 생성하기 보다, Collection 자료형으로 생성하면, Collection 자료형이 LinkedList와 HashMap 두 자료형으로 치환이 가능하기 때문에 다른 자료형을 사용하더라도, Collection의 서브 타입이라면 문제없이 코드가 진행됩니다.

 

네번째로 인터페이스 분리 원칙 (Interface Segregation Principle)입니다. 인터페이스 분리 원칙은 모든 클래스는 다양한 인터페이스를 상속하여 사용하는데, 이때 반드시 사용하는 메소드만 의존하게 끔하고, 이외에는 의존하지 않도록 인터페이스를 분리하는 원칙입니다. 예를들어, 동물 인터페이스를 만들고 Bow 메소드를 정의했는데, 어떤 동물은 짖기도 하지만, 어떤 동물은 짖지않습니다. 이럴 때, 모든 동물이 동물 인터페이스를 상속받으면 안짖는 동물도 Bow 메소드를 구상 메소드로 작성해야하므로 의존성이 생깁니다. 이를 방지하기 위해 인터페이스를 분리하여 따로 사용합니다.

 

마지막으로 의존 관계 역전 원칙 (Dependency Inversion Principle)입니다. 의존 관계 역전 원칙은 의존 관계를 맺을 때, 자신보다 변화하기 쉬운것에 의존하지 않고, 거의 변화가 없는 개념에 의존해야하는 원칙으로, 예를들어 아이가 장난감을 가지고 노는데, 이를 객체지향에서 구현하기 위해 Kid 클래스와 Robot 클래스를 만들고, Kid 클래스가 Robot 클래스의 인스턴스를 받아 역할을 하는 메소드를 Kid 클래스 내에 작성해야합니다. 이때, Doll 클래스가 따로 생긴다면, Kid 클래스는 이를 위해 또 코드 작성을 해주어야합니다. 즉, 아이가 갖고노는 장난감은 굉장히 변하기 쉽습니다. 그런데 각 장난감에 다 의존성을 부여하게되면 장난감이 추가될때마다 또 코드를 추가해야하는 코드를 작성하게 됩니다. 이를 방지하기 위해 Kid 클래스는 Toy 라는 인터페이스를 상속받고, 각 장난감은 Toy 인터페이스를 상속받아서 작성하게 됩니다. 이렇게 되면 Kid 클래스는 거의 변하지 않는 Toy 인터페이스에 의존성을 가지고, 장난감이 추가되더라도 Kid 클래스는 수정이 일어나지 않습니다.

 

내용이 긴 관계로 함수형 언어는 다음 포스트에서 작성하겠습니다.

반응형

'Computer Science > Others' 카테고리의 다른 글

함수형 언어  (0) 2024.03.27
공격  (0) 2024.03.25