기술 면접 준비(컴퓨터공학)
들어가기에 앞서
기술면접 준비를 위해 공부하는 내용을 정리하는 포스트이다.
처음 공부하는 내용이기 때문에 전혀 요약하지 않았다. 알못이라 요약할 수 없다..
인터넷 자료에 많이 의존해서 작성하고 있고 전혀 모르는 내용에 대해 작성하고 있으므로 나중에 꼭 서적 찾아보면서 진위 검증하기..!
알고리즘에 대해서는 주력 분야이므로 따로 포스트로 정리하고 있으니 여기에는 잘 모르는데 뭔가 중요하다고 한 것 같은 것들을 열심히 공부해보자!!
객체지향 프로그래밍
추상화
추상화는 어떤 영역에서 필요로 하는 속성이나 행동을 추출하는 작업을 의미한다. 즉, 주어진 구체적인 것들에서 공통된 특징을 파악해 이를 하나의 개념(집합)으로 다루는 수단이 추상화다.
구체적으로 프로그래밍에서는 다음과 같은 것이 추상화다.
- 공통의 속성이나 기능을 묶어 이름을 붙이는 것
- 클래스를 정의하는 것
캡슐화
소프트웨어 공학에서 요구사항 변경에 대처하기 위한 설계 원리로는 다음이 있다.
- 응집도 : 클래스나 모듈 안의 요소들이 얼마나 밀접하게 관련되어 있는지
- 결합도 : 어떤 기능을 실행하는 데 다른 클래스나 모듈들에 얼마나 의존적인지
높은 응집도와 낮은 결합도를 유지할 수 있도록 설계해야 한다. 즉, 내부 요소끼리만 밀접하게 관련되어있고 다른 클래스나 모듈에는 의존하지 않아야 한다.
캡슐화는 정보 은닉을 통해 낮은 결합도를 유지할 수 있도록 하는 객체지향 설계 원리다.
만일 정보은닉이 되어있지 않아 B클래스에서 A클래스의 내부 요소에 접근할 수 있다면, 굳이 메소드를 통해 접근하지 않고 직접 요소에 접근하는 방식으로 프로그래밍 한다면 두 클래스는 강한 결합이 발생한다. 따라서 A클래스의 내부 요소의 구조변경이 일어났을 때 B클래스까지 모두 수정을 거쳐야 하는 끔찍한 일이 일어날 것이다. 여기서는 두 클래스의 예시였지만 만일 여러 클래스에서 A클래스에 이런 방식으로 접근했다면..
- 관련있는 변수와 메소드를 묶어주는 것
- (JAVA)접근 제어 지시자를 통해 외부 접근을 제한하며 이를 정보은닉이라고 한다.
일반화(상속)
- 자식 클래스는 상속을 통해 부모 클래스의 속성과 기능을 물려받는다.
- 상속받은 기능의 일부를 수정하여 사용이 가능하다.(Overriding, 오버라이딩)
- 참고로 오버로딩(Overloading)은 같은 이름의 메소드를 매개변수의 자료형이나 개수를 달리하여 여러 개 정의 하는 것! 오버라이딩과 헷갈리니 잘 정리해두자.
단순히 상속을 통해 속성과 기능을 재사용 할 수 있도록 하는 개념이 아니다. 예를 들어 내가 원하는 기능이 구현된 클래스를 상속받아 사용했을 때, 사용하지 않는 다른 메소드를 통해 내부 변경이 가능하다면 큰 버그가 될 수 있다.
일반화 관계는 ‘is a kind of 관계’가 성립되어야 한다. 불필요한 속성이나 연산은 물려받지 않는것이 좋다.
특정 클래스의 일부 기능만 재사용하고 싶은 경우에는 위임(delegation)을 사용하면 된다.(C++에서 본 Delegate가 바로 이런건가보다!! 매우 흥미로워진다! 빨리 C++도 공부하고싶다)
위임은 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 위임하는 것이다.
- 일반화 관계 : 클래스 사이의 관계
- 위임 관계 : 객체 사이의 관계
위임을 사용해 일반화를 대신하는 과정은 다음과 같다.
- 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만든다. 이 속성 필드를 this로 초기화한다.
- 서브 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경한다.
- 서브 클래스에서 일반화 관계 선언을 제거하고 위임 속성 필드에 슈퍼 클래스의 객체를 생성해 대입한다.
- 서브 클래스에서 사용된 슈퍼 클래스의 메서드에도 위임 메서드를 추가한다.
음.. 천천히 이해해봤는데, 내가 이해한 식으로 다시 고쳐적자면 다음과 같다.
위임을 통해 다른 클래스의 속성이나 기능을 재사용하는 법!
- 클래스에 사용하고 싶은 클래스(부모 클래스) 변수를 선언한다.(객체를 생성해 대입한다.)
- 내가 사용하고 싶은 부모 클래스의 메소드는 모두 1번을 통해서 접근하면 된다.
두둥! 다시 생각해보니 이전에 유니티 프로젝트할 때 이런 방식을 썼던 것이 생각난다. 다른 클래스에서 정의했던 메소드만 쓰고싶어서 주먹구구식으로 썼던 방법이었다. 사용했던 방식의 개념을 알게 되니 신비롭다.
다형성
다형성은 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력이라고 한다. 일반적으로 다형성은 (이전에 내가 배운 바로는) 하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석되는 것이다. 다형성을 실현하기 위해 오버로딩과 오버 라이딩이 있다.
오버로딩하여 작성할 때는 메소드 이름만 동일하고 매개변수의 갯수나 타입을 다르게 하여 마음대로 재작성 할 수 있지만, 오버라이딩을 할 때는 메소드 명과 매개변수, 타입, 리턴타입을 모두 동일하게 해야 한다.
표로 정리하자면 아래와 같다.
구분 | 오버로딩 | 오버라이딩 |
---|---|---|
메소드 이름 | 동일 | 동일 |
매개변수, 타입 | 다름 | 동일 |
리턴 타입 | 상관없음 | 동일 |
SOLID 원칙(객체지향 5대 원칙)
SRP, OCP, LSP, ISP, DIP의 5가지 디자인 원칙의 앞글자를 딴 것이다.
SRP(Single Responsiblity Principle, 단일 책임 원칙)
- 소프트웨어의 설계 부품(클래스, 함수 등)은 단 하나의 책임(기능)만을 가져야 한다.
- 응집도는 높고 결합도는 낮게
책임이란, 해야 하는것, 할 수 있는 것, 해야 하는 것을 잘 할 수 있는것, 그리고 변경시 변경을 해야 하는 이유이다.
따라서 책임, 즉 기능을 많이 가지고 있을 수록 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 높은 결합도를 가질 가능성이 높아진다.
응집도가 낮고 결합도가 높아질 수록 변경사항이 발생했을 때 영향받는 코드의 범위가 넓어진다. 이는 유지보수 비용과 변경시 비용(cost)가 많아진다는 뜻이다.
어떤 변화가 있을 때 해당 변화가 기존 시스템의 기능에 영향을 주는지 평가하는 테스트를 회귀(regression)테스트라고 한다.
한 클래스가 여러 기능을 담당하지 않도록 클래스를 분리하는 것을 책임 분리라고 한다.(결합도를 낮추는 과정)
반대로 하나의 기능이 여러 개의 클래스 들로 분산되어 있는 경우(ex: 횡단관심(cross-cutting concern))에도 단일 책임 원칙에 의해 설계 변경이 필요 하다. 이를 산탄총 수술(shotgun surgery)이라고 한다.(응집도를 높이는 과정)
횡단 관심 문제를 해결하는 방법으로 관심지향 프로그래밍(AOP, Aspect-Oriented Programming) 기법이 있다. 횡단 관심을 수행하는 코드를 aspect라는 특별한 객체로 모듈화하고 weaving이라는 작업을 통해 모듈화한 코드를 핵심 기능에 끼워넣을 수 있다.
OCP(Open-Closed Principle, 개방-폐쇄 원칙)
- 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다.
- 객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다는 원칙.
- 대표적인 문법은 인터페이스(Interface)
- 클래스를 변경하지 않고도(closed) 대상 클래스의 환경을 변경할 수 있는(open) 설계가 되어야 한다.
LSP(Liskov Subsitution Principle, 리스코프 치환 원칙)
- 자식 클래스는 자신의 부모 클래스의 기능을 수행할 수 있어야 한다.
- 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙.
- 부모 클래스와 자식 클래스 사이의 행위가 일관성이 있어야 한다.
어떤 클래스의 행위를 일종의 방정식 형태로 기술해 자식 클래스의 인스턴스가 이 방정식을 만족하는지 점검해 LSP를 만족하는지 점검한다.
LSP를 만족시키는 가장 간단한 방법은 오버라이드(재정의)를 하지 않는 것이다.
ISP(Interface Segregation Principle, 인터페이스 분리 원칙)
- 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 한다.
- 인터페이스를 클라이언트에 특화되도록 분리시켜라.
- 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
- 하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다.
- SRP를 만족하더라도 ISP를 반드시 만족한다고는 할 수 없다.
DIP(Dependency Inversion Principle, 의존 역전 원칙)
- 의존 관계를 맺을 때, 구체적인 클래스(변화하기 쉽거나 자주 변화하는 것)보다 인터페이스나 추상 클래스(변화하기 어렵거나 거의 변화가 없는 것)와 관계를 맺어야 한다.
- 상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존해서는 안된다.
- 추상화 된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
- DIP를 만족하면 ‘의존성 주입(DI, Dependency Infection)’ 기술로 변화에 유연한 설계를 할 수 이다.
참고
디자인 패턴
(사담) 말은 많이 들어봤는데 의미를 전혀 몰라 많이 궁금했던 개념이다.
디자인 패턴의 개념
디자인 패턴(Design pattern)은 건축학 및 컴퓨터 과학에서 사용되는 용어로, 설계 문제에 대한 해답을 문서화하기위해 고안된 형식 방법이다. 각 영역에서 관련된 패턴들을 구조적으로 정리한 것을 패턴 언어라고 부른다.
즉, 공통적으로 발생하는 문제에 대해 이미 만들어진 해결책을 패턴으로 정해놓고, 해당 문제에 직면했을 때 “Singleton 패턴을 사용하자!” 하는 식이다.(즉 각 상황에 따라 어떤 패턴이 쓰이는지 자주 쓰이는 패턴을 어느정도 숙지하고 있어야 한다는…..이야기 같다..)
그렇지만 다른 포스트에서는 ‘실무 프로그래머들이 인정한 효율적인 코딩방법 or 구조’라고 표현하고 있다. 즉, 문제에 직면했을 때만 사용하는 것이 아니라 어느정도 규격화된 프로그래밍을 위해 미리 정해둔 구조라고 생각하면 될 것 같다.
알고리즘이랑 비슷한 것 같다. 즉, 무작정 패턴을 외우기보다는 직접 코딩에 적용하면서 체득하고 이후에 어떤 상황에 직면했을 때 자연스럽게 적용해야 할 패턴을 떠올리고 적용할 수 있는가가 중요하다고 한다.
패턴은 공통의 언어를 만들어주며 팀원 사이의 의사 소통을 원활하게 해주는 아주 중요한 역할을 한다.
디자인 패턴의 구조
- 콘텍스트(Context) : 문제가 발생하는 여러 상황을 기술한다. 즉, 패턴이 적용될 수 있는 상황을 나타낸다. 경우에 따라서는 패턴이 유용하지 못한 상황을 나타내기도 한다.
- 문제(problem) : 패턴이 적용되어 해결될 필요가 있는 여러 디자인 이슈들을 기술한다. 이 때 여러 제약 사항과 영향력도 문제 해결을 위해 고려해야 한다.
- 해결(solution) : 문제를 해결하도록 설계를 구성하는 요소들과 그 요소들 사이의 관계, 책임, 협력 관계를 기술한다. 해결은 반드시 구체적인 구현 방법이나 언어에 의존적이지 않으며 다양한 상황에 적용할 수 있는 일종의 템플릿이다.
GoF 디자인 패턴
분류
생성 패턴 | 구조 패턴 | 행위 패턴 |
---|---|---|
추상 팩토리(Abstract Factory) | 어댑터(Adapter) | 책임 연쇄(Chain of Responsibility) |
빌더(Builder) | 브리지(Bridge) | 커맨드(Commend) |
팩토리 메서드(Factory Method) | 데커레이터(Decorator) | 인터프리터(Interpreter) |
프로토타입(Prototype) | 퍼사드(facade) | 이터레이터(Iterator) |
싱글턴(Singleton) | 플라이웨이트(Flyweight) | 미디에이터(Mediator) |
프록시(Proxy) | 메멘토(Memento) | |
옵서버(Observer) | ||
스테이트(State) | ||
스트래티지(Strategy) | ||
템플릿메서드(Template Method) | ||
비지터(Visitor) |
- 생성(Creational) 패턴
- 객체 생성에 관련된 패턴이다.
- 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다.
- 구조(Structural) 패턴
- 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다.
- 예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묵어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.
- 행위(Begavioral) 패턴
- 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴이다.
- 가령 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화 하는 것에 중점을 둔다.
생성 패턴
싱글턴(Singleton) 패턴
(사담)유니티 프로젝트를 하면서 정말정말정말 많이 들었던 이름이었다. 당시에 듣고 이해를 잘 못해서 일단 모든 변수는 그냥 private로 선언했고 변수 접근하는 것은 다 메소드를 통해 처리하도록 했었다. 대신 최대한 당시의 객체지향 프로그래밍은 이래야 한다(캡슐화와 모듈화를 가장 잘 지키려고 노력했던 것 같다)라고 자바 시간에 배웠던 것을 되새기면서 코딩하려고 노력했던 것 같다. 당시에는 워낙 멀티스레드를 쓸 일이 많았기 때문에 싱글턴 패턴 적용에 주의해야 할 점이 많았을 것이다.
- 전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴.
중복 문제는 사라지지만 속도는 느려진다.
예시) 생성자를 private처리하고 따로 인스턴스를 생성할 수 있는 메소드를 선언해 메소드 내에서 이미 생성되어 있는 인스턴스가 존재하는지 확인하고 존재하지 않을 경우에는 생성자를 통해 인스턴스를 생성, 존재할 경우에는 기존에 생성된 인스턴스를 반환한다.
따라서 하나의 인스턴스만을 생성하는 책임(기능)을 가지고 모든 클라이언트에게 동일한 인스턴스를 반환한다.
주의해야 하는 점은 getInstance 메서드와 메서드를 사용하기 위한 변수는 정적으로 선언하여 클래스의 인스턴스를 통하지 않고서도 메서드 실행과 변수 참조가 가능해야 한다.
문제점
다중 스레드에서 인스턴스가 여러개 생성되는 경우가 발생할 수 있다. 즉, 경합조건이 발생하는 경우다.
경합조건
동일한 자원을 2개 이상의 스레드가 이용하려고 경합하는 현상이다.
- 발생 상황 예시
- 스레드 1이 getInstance()를 실행하여 if문을 통해 인스턴스가 생성되지 않았음을 확인하고 생성자를 호출하려 할 때, 스레드 1이 생성자 호출하기 이전에 스레드 2가 getInstance()를 실행해 if문으로 인스턴스의 존재 여부를 확인한 경우, 두 스레드 모두 인스턴스를 생성하는 코드를 실행하게 된다.
이는 해당 클래스의 인스턴스가 상태를 유지해야 한다면 문제가 발생한다. 이는 인스턴스마다 변수를 각각 만들어 유지하기 때문이다.
해결책
- 다중 스레드 애플리케이션이 아닌 경우에는 문제가 되지 않는다.
- 다중 스레드 애플리케이션에서의 해결책은 다음의 두가지가 있다.
- 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법 (Eager Initalization)
- 정적변수의 특징을 이용한 것이다.
- 정적 변수는 객체가 생성되기 전 클래스가 메모리에 로딩될 때 만들어져 초기화가 한 번만 실행된다.
- 정적변수는 프로그램의 시작부터 종료까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12
public class Printer { // static 변수에 외부에 제공할 자기 자신의 인스턴스를 만들어 초기화 private static Printer printer = new Printer(); private Printer() { } // 자기 자신의 인스턴스를 외부에 제공 public static Printer getPrinter(){ return printer; } public void print(String str) { System.out.println(str); } }
- 다중 스레드 환경에서 문제를 일으켰던
if(printer==null)
라는 조건 검사 구문을 제거하기 위한 방법이다.
- 정적변수의 특징을 이용한 것이다.
- 인스턴스를 만드는 메서드에 동기화하는 방법 (Thread-Safe Initializaion)
- 인스턴스를 얻어오는 메서드와 공유 변수에 접근하는 부분을 동기화한다.(임계구역으로 변경한다.)
- 임계영역 : 하나의 스레드가 사용하고 있을 때 다른 스레드는 대기해야 하는 영역
- 메서드의 임계영역 지정 : 인스턴스 생성시 경합조건 발생 방지
- 공유변수의 임계영역 지정 : 여러 스레드가 하나의 공유변수에 동시에 접근해 갱신하는 것을 방지
- getInstance()에 Lock을 하는 방식이라 속도가 느려진다.
- 인스턴스를 얻어오는 메서드와 공유 변수에 접근하는 부분을 동기화한다.(임계구역으로 변경한다.)
- 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법 (Eager Initalization)
정적 클래스
정적 클래스를 사용하면 싱글턴 패턴을 사용한 것과 같은 효과를 낼 수 있다.
싱글턴 패턴과의 차이점
- 객체를 전혀 생성하지 않고 메서드를 사용한다
- 인스턴스 메서드를 사용하는 것 보다 성능 면에서 우수하다.(인스턴스 메서드는 컴파일 타임시 바인딩되기 때문이다.)
정적 클래스의 사용이 불가능한 경우
- 인터페이스를 구현해야 하는 경우에는 사용이 불가능하다.
- 인터페이스를 사용하는 주된 이유는 대체 구현이 필요한 경우다.
- 이는 특히 모의 객체(Mock 객체)를 사용해 단위 테스트를 수행할 때 중요하다.
- 단위 테스트 실행시 사용되는 인스턴스를 테스트용 가짜 객체로 대체하는 것이 좋다. 실제로 사용되는 객체의 경우 병목 현상이 발생할 수 있고, 단위 테스트는 빠른 실행이 가장 중요한 특성이기 때문이다.
팩토리 메서드(Factory Method) 패턴
- 객체 생성처리를 서브 클래스로 분리해 처리하도록 캡슐화하는 패턴
- 객체를 생성하는 인터페이스를 정의하고 인스턴스를 만드는 클래스는 서브 클래스에서 결정하도록 하는 패턴
- 스트래티지 패턴, 싱글턴 패턴, 템플릿 메서드 패턴을 사용한다.
예시) 스케줄링 전략을 변경하거나, 프로그램 실행중에 스케줄링 전략을 변경해야 하는 동적 스케줄링을 지원해야 하는 경우(이번 예시에서는 최대한 일반적인 예시를 사용하고자 노력했으니 스케줄링을 메서드나 방식등의 어휘로 치환할 경우 표현의 어려움이 있어 스케줄링의 예시를 그대로 들고오도록 한다.)
- 스케줄링 : 주어진 요청을 받았을 때 여러 인스턴스 중 하나를 선택하는 것.
스트래티지 전략을 이용해 인터페이스로 스케줄링 메서드를 작성해 다수의 스케줄링을 지원할 수 있다.
그러나 그럼에도 다음과 같은 문제가 존재한다.
- 새로운 스케줄링 전략이 추가되는 경우
- 동적 스케줄링 방식이 변경되는 경우. 예를들어 A->B의 순서에서 B->A로 변경되거나, 새로운 전략을 포함해야 하는 경우 등.
이런 경우 기존의 코드를 수정해야 하고, 이럴 경우 기존 메서드의 책임에 영향을 미칠 수 있으므로 기존의 코드는 변경하지 않는 코드를 작성해야 한다.
해결책
- 주어진 기능을 실제로 제공하는 적절한 클래스 생성 작업을 별도의 클래스/메서드로 분리시킨다.
예를 들어 본래 ElevatorManager에서 바로 스케줄링 클래스의 객체들을 생성해 사용했다면, Factory클래스를 만들어 getScheduler()메서드가 스케줄링 전략에 맞는 객체를 생성하고 ElevatorManager에서는 반환받은 객체만을 사용하게 되는 것이다. - 여러 번 객체를 생성하지 않고 한 번 생성한 객체를 계속 사용하는 것이 바람직할 수 있다. 즉, 싱글턴 패턴을 적용한다.
- 각 생성자를 private로 정의하고, 정적 메서드를 통해 객체를 생성한다.
- 이처럼 Factory클래스를 따로 둘 경우 Factory클래스에서 주어진 조건에 맞는 스케줄러 객체를 생성한다.
- 팩토리 메서드 패턴은 하위 클래스에서 적합한 클래스의 객체를 생성하는 방식으로도 적용할 수 있다. 즉, 템플릿 메서드 패턴을 적용하여 구현한다.
- 각 스케줄링 전략에 따른 기능을 수행하는 클래스를 상위 클래스의 하위 클래스로 정의할 수 있다.
- 하위 클래스에서 적합한 클래스의 객체를 생성하여 객체의 생성 코드를 분리한다.
- 이 경우 팩토리 메서드는 객체 생성이 분리된 클래스를 말한다.
예시에서의 ElevatorManager 클래스의 getScheduler()메서드- 템플릿 메서드에서의 primitive or hook메서드. 하위 클래스에서 오버라이드될 필요가 있기 때문이다.
- 템플릿 메서드는 공통 기능의 일반 로직을 제공하는 메서드이다.
- 예시에서의 ElevatorManager 클래스의 requestElevator()메서드
- primitive or hook 메서드를 포함하고 있는 메서드
- 팩토리 메서드를 호출하는 상위 메서드는 템플릿 메서드가 된다.
- 역할이 수행하는 작업은 다음과 같다.
- Product
- 팩토리 메서드로 생성될 객체의 공통 인터페이스
- Concrete Product
- 구체적으로 객체가 생성되는 클래스
- Creator
- 팩토리 메서드를 갖는 클래스
- ConcreteCreator
- 팩토리 메서드를 구현하는 클래스, Concrete Product 객체를 생성.
- Product
구조 패턴
어뎁터
행위 패턴
스트래티지(Strategy) 패턴
- 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 하는 패턴
- 동일 계열의 알고리즘들을 정의하고, 각각 캡슐화하며 이들을 상호교환 가능하도록 하는 것. 알고리즘을 사용하는 사용자로부터 독립적으로 알고리즘이 변경될 수 있도록 함.
- 같은 문제를 해결하는 여러 알고리즘(방식)이 클래스로 캡슐화되어있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게하는 패턴.
예시) 기존의 기능을 수정하거나 기존의 클래스와 유사하지만 일부 기능을 수정하고 새로운 기능을 덧붙인 클래스를 만들고 싶다면?
- 기존의 기능을 수정하는 경우 기능을 변경하기 위해 기존 코드의 내용을 수정하는 것은 OCP에 위배된다.
또한 OCP를 지키기 위해 서브 클래스의 기능을 변경했을 때 다른 클래스의 메서드와 중복된 기능을 가지게 될 수도 있다. 중복상황은 기능의 수정이 있을 때 모든 중복코드를 일관성 있게 수정해야 하는 문제를 만들어낸다. 따라서 최대한 중복코드를 줄이는 것이 좋다. - 새로운 클래스를 추가하고 기능을 변경/추가하는 경우 기존의 클래스와 유사한 새로운 클래스를 만들 경우 기존 클래스와 완전히 동일한 기능을 가지는 메소드가 발생할 수 있다. 즉, 중복 코드가 발생한다. 중복 코드는 위에서 언급했듯이 심각만 문제를 발생시킨다.
해결책
변화된 것을 찾은 후 클래스로 캡슐화한다.
캡슐화를 위해 외부에서 구체적인 기능들을 담은 구체적인 클래스드를 은닉해야 한다. 이를 위해 해당 기능을 위한 인터페이스를 각각 만들고 실제로 실현한 클래스는 따로 만든다.
인터페이스들이 부모 클래스의 변경을 차단해준다.
새로운 기능의 추가가 기존의 코드에 영향을 미치지 못하므로 OCP를 만족하는 설계가 된다.
외부에서 로봇 객체의 이동, 공격 방식을 임의대로 바꾸도록 해주는 메서드가 필요하다. 이렇게 변경이 가능한 이유는 상속 대신 집약 관계를 이용했기 때문이다.
- 역할이 수행하는 작업은 다음과 같다.
- Strategy
- 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시
- ConcreteStrategy(그림에서는 Strategy1, 2…)
- 스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스
- Context
- 스트래티지 패턴을 이용하는 역할
- 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메서드 제공
- Strategy
템플릿 메서드(Template Method) 패턴
- 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴
- 객체의 연산에서 알고리즘의 뼈대만 정의하고, 나머지는 서브 클래스에서 이루어지게 하는 패턴. 알고리즘의 구조는 변경하지 않고 알고리즘의 각 단계를 서브 클래스에서 재정의하게 된다.
템플릿 메서드 패턴은 전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화할 때 유용하다.
동일 한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.
예시) 코드 중복 문제로 인해 상속을 이용하여 추상 클래스를 정의하고 서브 클래스를 정의했을 때, 서브 클래스의 메소드에 여전히 코드 중복 문제가 발생하는 경우
해결책
코드가 중복되는 부분은 상위 클래스에 작성하고 다른 부분은 상위 메소드에서 추상(abstract)메서드로 정의한 후 각 하위 클래스에서 오버라이드(특수화)한다.
- 여기서 추상 메서드를 포함해 상위 클래스에 정의된 메서드를 템플릿 메서드라고 부르고, 하위 클래스에서 오버라이드 될 필요가 있는(추상 메서드로 정의된 것) 메서드를 primitive 또는 hook메서드라고 부른다.
- 역할이 수행하는 작업은 다음과 같다.
- AbstractClass
- 템플릿 메서드를 정의하는 클래스
- 하위 클래스에 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive 메서드 또는 hook 메서드로 정의하는 클래스
- ConcreteClass
- 물려받은 primitive 메서드 또는 hook 메서드를 구현하는 클래스
- 상위 클래스에 구현된 템플린 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메서드나 hook 메서드를 오버라이드 하는 클래스
- AbstractClass
참고
인공지능
머신러닝
모두를 위한 딥러닝 강의
머신러닝을 공부하던 레포지토리
머신러닝은 이전까지 모든 예외와 동작을 일일이 코딩해야 했던 기존의 explicit 프로그래밍과 달리, 컴퓨터가 스스로 학습하고 발전하여 결과를 낸다는 점에서 명확한 차이가 있다.
머신러닝은 학습 방법의 차이에 따라 다음의 세 가지로 나뉜다.
- 현재까지의 데이터를 통해 미래를 예측하는 것이 궁극적인 목표
- Supervised Learning(지도 학습법)
- 정해진 data(labeled-tranning set)로 학습한다.
- 일반적 문제를 해결하는데 쓰인다.
- 타입
- regression(회귀)
- 연속된 값을 예측하는 문제
- 예를 들면 공부 시간에 따른 시험 점수 예측.
- classification(분류)
- 주어진 데이터를 정해진 카테고리에 따라 분류하는 문제
- label의 수에 따라 다음의 두가지로 나뉜다.
- binary classification
- label이 2가지이다. 즉, 맞다 / 아니다로 구분된다.
- 예시 : 공부 시간에 따른 해당 과목의 Pass / fail 예측
- multy-label classification
- 여러 label중에 하나를 고른다.
- 예시 : 공부 시간에 따른 학점 예측
- binary classification
- regression(회귀)
- Unsupervised learning(비지도 학습법)
- 미리 정답(레이블)을 정해서 주지 않는다.(un-labeled data)
- 데이터의 연속 여부에 따라 다음의 두가지로 나뉜다.
- Categorial data(분리되어있는 데이터)
- Continuous data(연속적인 데이터)
- Reinforcement Learning(강화 학습법)
- 자신이 한 행동에 대한 보상을 통해 학습해나가는 것.
- 알파고의 학습법으로 유명하다.
딥러닝, 그리고 인공신경망이란?
- 딥러닝
- 머신러닝의 일종
- 인간의 뇌의 동작 방식에서 착안된 학습 방식이다.
- 인공 뉴런으로 이루어진 인공 신경망을 이용해 구성한다.
- 하나의 인공 신경망은 여러 개의 레이어로 구성될 수 있고, 하나의 레이어는 여러 개의 인공 뉴런으로 구성될 수 있다. 각 은닉층 안에 존재하는 인공 뉴런의 갯수와 레이어의 갯수는 모두 하이퍼파라미터 값으로 특별한 정답이 없다. 그러나 하이퍼파라미터 값을 적절히 잘 설정해야 좋은 성능의 프로그램을 만들수 있다.
Comments powered by Disqus.