그냥 블로그

[객체 지향] 객체 지향 설계 원칙 SOLID 본문

CS/기타

[객체 지향] 객체 지향 설계 원칙 SOLID

코딩하는 공대생 2024. 7. 22. 12:50
반응형
인터페이스와 클래스의 차이( JavaScript )

 

인터페이스
ES6가 제공하지 않는 TS 만의 특징. 추상 클래스는 선언과 구현이 모두 존재하지만, 인터페이스는 선언만 존재.

멤버 변수와 멤버 메서드를 선언할 수 있지만 접근 제한자는 설정할 수 없다.

 

클래스

청사진을 정의한 다음 클래스 속성을 추기화하고, method를 정의한다. 클래스의 인스턴스를 만들 때 실행 가능한 함수와 정의된 property를 가진 객체를 얻는다. 

 

 

JAVA 에서 추상 클래스와 인터페이스

https://hahahoho5915.tistory.com/70

 

 

 

정리
  • 단일 책임 원칙 (SRP)
  • 개방 폐쇄 원칙 (OCP)
  • 리스코프 치환 원칙 (LSP)
  • I가 뭐더라 -> 인터페이스 분리 원칙 (ISP)
  • 의존 역전 원칙 (DIP)
단일 책임 원칙

 

모든 클래스는 하나의 기능만 가지며, 클래스가 제공하는 보든 서비스는 그 하나의 책임을 수행하는 데 집중되어야 한다. 
어떤 변화에 의해 클래스를 변경하는 이유도 오직 하나 뿐이다. 

 

장점 : 책임 영역이 확실해지기 때문에, 한 책임의 변경에서 다른 책임 변경으로의 연쇄작용에서 자유로움. 가독성, 유지보수성 용이. 

 

적용 방법 : 리팩토링

1) 여러 원인에 의한 변경 (Divergent change) :
Extract Class를 통해 혼재된 각 책임을 각각의 개별 클래스로 분할. 책임만 분리하는 것이 아니라, 두 클래스간 관계의 복잡도를 줄이도록 설계한다.
유사하고 비슷한 책임을 중복해서 갖고 있다면 Extract SuperClass 사용 ( 부모 클래스를 두고 부모에 위임 )

 

2) 산탄총 수술 (Shotgun surgery )

Move Field 및 Move Method로 책임을 기존의 어떤 클래스로 모으거나 새로 만들어 모음. 산발된 책임을 응집. 응집도 향상

 

=> Serila Number은 고유 속성 ( 변화 불가 )
=> price, Maker, Type, modl, backWood, topWood, stringNum 등은 변경이 발생 가능 ( 변화 요소 )

와 이렇게 까지??????ㅋㅋㅋㅋㅋㅋㅋ

 

개방폐쇄 원칙

 

추상화, 다형성

1998년 객체지향 소프트웨어 설계라는 책에 정의된 내용으로 소프트웨어 구성요소 ( 컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고 변경에는 닫혀있어야 한다.

요구사항 변경, 추가사항 발생 시에 기존 구성요소 수정이 아닌 쉽게 확장해서 재사용 해야 한다는 것. 

 

 

장점 : 재사용 가능한 코드를 만든다. 객체지향의 장점을 극대화 한다. 결합도는 줄이고 응집도는 높인다.

 

적용 방법: 

1) 변경(확장)될 것과 변하지 않을 것을 엄격히 구분. 

2) 두 모듈이 만나는 지점에 인터페이스 정의.

3) 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드 작성. 

=> 단일 책임을 했지만, 기타 외에 바이올린, 비올라 같은 다른 종류의 악기가 들어온다면 스펙을 일일이 만들 것이냐? 에바쥐 
-> 부모 클래스를 선언해서 악기들을 추상화. 공통 속성은 새로운 인터페이스인 StringInstrument에 담는다. 

=> 적당한 추상화 레벨 선택이 필요하다. 

=> 가능한 변경되면 안되기 때문에 적당한 예지력이 필요하다....;;

 

리스코프 치환 원칙 (LSP)

 

다형성 

서브 타입은 언제나 기반 타입으로 교체 가능해야 한다. 

다형성을 지키기 위한 원칙이다. 

서브 타입은 기반 타입이 약속한 규약 (public 인터페이스, 메소드가 던지는 예외까지..) 지켜야한다. 
상속은 구현상속(extends 관계)이든 인터페이스 상속(implements 관계) 이든 궁극적으로 다형성을 통한 확장성 획득을 목표로 한다. LSP도 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미함. 

Abstract Factory 등 패턴을 사용해 유연성을 높임. -> 상속 재사용은 is-a 관계일 때. 외 경우넨 합성을 이용한 재사용. 

 

적용 방법 

1) 만약 두 개체가 같은 일을 한다면 둘을 하나의 클래스로 표현하고 구분할 수 있는 필드를 둔다. 

2) 똑같은 연산을 제공하지만, 이들을 약간 다르게 한다면 공통 인터페이스를 만들고 둘이 이를 구현한다. ( 인터페이스 상속) 

3) 공통된 연산이 없다면 완전 별개인 2개 클래스 

4) 만약 두 개체가 하는 일에 추가적으로 무언가 더 한다면 구현 상속 이용. 

 

=> 여기까지는 공통적인 일을 하년 인터페이스를 뽑아내고, 거기서 약간의 변경 사항은 오버라이딩을 이용하는 방식이라고 생각됨. 
-> 오버라이딩 시에 자식 클래스가 메소드 시그니처를 멋대로 변경하거나 의도와 다르게 메소드를 오버라이딩 하면 안된다. 

 

void myData() {
	// Collection 인터페이스 타입으로 변수 선언
    Collection data = new LinkedList();
    data = new HashSet(); // 중간에 전혀 다른 자료형 클래스를 할당해도 호환됨
    
    modify(data); // 메소드 실행
}

void modify(Collection data){
    list.add(1); // 인터페이스 구현 구조가 잘 잡혀있기 때문에 add 메소드 동작이 각기 자료형에 맞게 보장됨
    // ...
}

예시로 많이 드는 Java의 Collection 프레임워크

 

=> 변수에 LinkedList 자료형을 담아 사용하다 중간에 전혀 다른 HashSet으로 변경해도 add() 메서드를 그대로 보장받기 위해 Collection이라는 인터페이스 타입으로 변수를 선언해 할당한다. 
=> 왜냐, Collection의 추상 메서드를 각기 하위 자료형 클래스에서 implements 하여 인터페이스 구현 규약을 잘 지키도록 미리 설계되어 있기 때문이다. 

=> 음.... 애매....하네.... 음...뭔가 동일한 일을 하는 클래스들이 있으면, 걔네를 하나로 합쳐서 상속으로 특성별로 분리하고 오버라이딩으로 같은 로직을 원하는 조건을 추가해서 할 수 있게 하는느낌..? 그래서 원하는 로직을 여러 data를 넣어도 가능하도록 다형성을 지키는 느낌???

 

상속이 어렵다면 합성을 사용해라.

 

인터페이스 분리 원칙

 

한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.  어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만 사용한다. 
어떤 클래스를 이용하는 클라이언트가 여러 개고, 해당 클래스의 특정 부분집합만 이용한다면 이를 따로 인터페이스로 빼내어 클라이언트가 기대하는 메시지만 전달. 

 

적용 방법 : 

1) 클래스 인터페이스를 통한 분리 :
클래스 상속을 이용해 인터페이스 분리.  클라이언트에 변화를 주지 않음. 하지만, 상속을 이용한 확장은 상속받는 클래스 성격을 디자인 시점에 규정해버린다. 따라서 인터페이스를 상속받는 순간 인터페이스에 예속되어 제공하는 서비스 성격이 제한된다. 

 

2) 객체 인터페이스를 통한 분리 : 

위임(Delegation)을 이용해 인터페이스를 나눈다. 다른 클래스 기능을 사용해야 하지만, 변경하고 싶지 않다면 상속 대신 위임을 쓴다. 

 

*위임? 특정 일의 책임을 다른 클래스나 메소드에 맡김. 

 

 

 

 

예시로 많이 드는 Java Swing의 JTable 

모든 인터페이스 분리를 통해 특정 역할만 이용가능하게 해줌. 자신을 이용해 테이블을 만드는 객체, 모든 서비스를 필요로 하는 객체에게는 기능 전부를 노출하지만, 이벤트 처리와 관련해서는 여러 리스너 인터페이슬르 통해 해당 기능만 노출. 

 

interface IPhone {
    void call(String number); // 통화 기능
    void message(String number, String text); // 문제 메세지 전송 기능
}

interface WirelessChargable {
    void wirelessCharge(); // 무선 충전 기능
}

interface ARable {
    void AR(); // 증강 현실(AR) 기능
}

interface Biometricsable {
    void biometrics(); // 생체 인식 기능
}

 

class S21 implements IPhone, WirelessChargable, ARable, Biometricsable {
    public void call(String number) {
    }

    public void message(String number, String text) {
    }

    public void wirelessCharge() {
    }

    public void AR() {
    }

    public void biometrics() {
    }
}

class S3 implements IPhone {
    public void call(String number) {
    }

    public void message(String number, String text) {
    }
}

 

=> 보면, 같은 휴대폰이지만 s21은 더 많은 기능이 포함되어 있다. 이 때, 휴대폰에 해당 기능을 모두 넣어놓는다면 s3에 있는 ar, biometrics 같은 기능들은 빈 껍데기로 존재해야한다. 그렇기 때문에 각 기능을 "인터페이스"로 분리해서 합성하는 방법이다.

 

의존성역전의 원칙 

 

IOC, 훅(hook), 확장성

하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의 역전. 실제 사용 관계는 바뀌지 않지만 추상을 매개로 메시지를 주고받음으로써 관계를 최대한 느슨하게 한다. 

복잡하고 지난한 컴포넌트 간 커뮤니케이션의 단순화

 

적용 방법 

1) Layering 

잘 구조화된 객체지향 아키텍처들은 각 레이어마다 잘 정의되고 통제되는 인터페이스를 통한 긴밀한 서비스들의 집합을 제공하는 레이어로 구성된다. 
이것은 단순 레이어를 통한 구조화뿐만 안라 Transitive Dependency(의존역전)이 발생했을 때 상위 레벨 레이어가 하위레벨 레이어를 바로 의존하지 않고 사이에 존재하는 추상레벨을 통해 의존하는 것을 이야기 한다.

상위레벨 모듈은 하위레벨 모듈의 의존성에서 벗어나 재사용되고 확장성을 보장받을 수 있다.

통신 프로그래밍 모델 ( EX_소켓 프로그램 ) 은 클라이언트가 서버에 요청을 send()하고 결과를 recv()하여 서버 서비스를 이용한다. send() & recv()를 하면 recv()함수는 블럭이되어 그 동안 스레드는 서버 응답이 오기를 대기한다. 
따라서, 대기 중에 recv()를 호출한 스레드는 다른 작업을 할 수 없기 때문에 자원이 낭비된다. 

이를 해결하기 위해 polling을 사용한다. 대기 중에 다른 작업을 수행하다가 응답을 받으면 서버의 메시지를 가져오는 것이다. 다른 작업을 하면서도 서버의 응답을 확인해야 한다. 여기까진, 모든 통제가 클라이언트 스레드 스케쥴 내에 있다. 

결국, 응답이 지연될 경우 계속 비용이 소모된다는 것, 서버의 응답을 확인하는 시도가 여러번 발생 시 폴링 모델 오버헤드가 발생한다. 


이게 DIP를 적용하기 적당한 시점이다. 클라이언트 스레드는 메시지를 SEND()한 후에 RECV()하는 대신 서버 응답을 처리하는 훅 메소드를 등록한다. 
- 구조적 프로그램에서는 함수 포인터를 등록하지만 객체지향 세계에서는 커맨드 오브젝트를 등록한다 (GoF 커맨드 패턴) 
-> 통제권이 클라이언트 스레드에서 커멘드 오브젝트로 역전되는 IOC가 전제된다. 
-> 퍼포먼스 향상, 클라이언트 응답 대기/확인 제거로 역할 단순화

 

=> 그러니까 리액트로 예를 들면, 뷰를 보여주는 곳에서 바로 요청을 보내고 응답 확인을 하지 않고 훅으로 따로 분리해서 뷰를 보여준느 곳에서는 그 시간에 다른걸 처리할 수 있고 응답 대기랑은 훅에서 한다 이말인가?

 

뭔가 비동기 요청되니까 컴포넌트는 다른 작업 수행 가능한거 같은데?

 

이게 스레드랑은 무슨 상관이지 -> JS는 단일 스레드 환경이어서 기본적으로 하나의 스레드에서 실행되는데, 이벤트 루프를 사용해서 비동기 작업을 한다. 
음...ㅋ....ㅋ...ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ;;;;

 

 

 

[내가 공부했던 JS 기록]

 

TIL-JavaScript/JavaScript at master · mina3215/TIL-JavaScript

[TIL] JavaScript에 관련된 모든 것. Contribute to mina3215/TIL-JavaScript development by creating an account on GitHub.

github.com

[SOLID 참조 글 : 강추 👍 ]

 

객체지향 개발 5대 원리: SOLID

현재를 살아가는 우리들은 모두 일정한 원리/원칙 아래에서 생활하고 있습니다. 여기서의 원칙 이라 함은 좁은 의미로는 개개인의 사고방식이나 신념, 가치관 정도가 될 수가 있겠고, 넓게는 한

www.nextree.co.kr

[리스코프 치환 원칙 : 추천 👍  ]

 

💠 완벽하게 이해하는 LSP (리스코프 치환 원칙)

리스코프 치환 원칙 - LSP (Liskov Substitution Principle) 리스코프 치환 원칙은 1988년 바바라 리스코프(Barbara Liskov)가 올바른 상속 관계의 특징을 정의하기 위해 발표한 것으로, 서브 타입은 언제나 기반

inpa.tistory.com

[인터페이스 분리 원칙 : 추천 👍 ]

 

💠 완벽하게 이해하는 ISP (인터페이스 분리 원칙)

인터페이스 분리 원칙 - ISP (Interface Segregation Principle) ISP 원칙이란 범용적인 인터페이스 보다는 클라이언트(사용자)가 실제로 사용하는 Interface를 만들어야 한다는 의미로, 인터페이스를 사용에

inpa.tistory.com

 

'CS > 기타' 카테고리의 다른 글

[CS] 함수형 프로그래밍과 불변성(immutable)  (0) 2024.07.24
정규 표현식 (Regex)  (10) 2024.07.24
[CS] sw 테스트 : 단위 테스트 (Jest)  (0) 2024.07.23
[CS] 파싱/ 컴파일러 이론  (0) 2024.07.17
[GIT] Git에 대해서  (0) 2024.07.15