본문 바로가기
개발자 준비/디자인패턴 & 아키텍쳐

OOP 설계 원리

by osul_world 2021. 12. 29.
728x90

OOP 설계 원리


 

SOLID

  • 가장 대표적인 OOP설계 원리로써 모든 패턴의 기반이다.

  • 아래 5가지 원칙을 갖는다.

  • SRP(Single Responsibility Principle)

    수정할 이유는 하나, 단일 책임만

  • OCP(OpenClosedPrinciple)

코드 수정 없이 확장이 가능해야 함

  • LSP(Liskov Substitution Principle)

상위 타입은 항상 하위 타입으로 교체가 가능해야 함

  • ISP(Inteface Segregation Principle)

필요 없는 것을 구현하도록 강요하지 않아야 함

  • DIP(DependencyInversion Principle)

의존관계역전(DIP):클래스는 구체적 클래스 대신에 상위 추상 타입이나 interface에 의존해야 함 또한 일방향이어야함

 

SOLID 5원칙 세부

SRP(Single Responsibility Principle)

클래스는 오직 한가지 책임만을 가진다!!

클래스는 여러 멤버 변수를 유지할 수 있고, 여러 메소드를 가질 수 있지만 SRP에 의하면 이들은 모두 공통으로 한 가지 책임을 위해존재한다.

OCP(OpenClosedPrinciple)

클래스는 확장에 대해서 열려 있지만 변화에 대해서는 닫혀 있어야 한다!!

코드의 수정없이 변경이 가능해야한다.

상속이나 DIP포함관계이용하여 구현할수있다.

다형성과 DIP 이용, 새로운 기능이 필요하면 새 클래스를 만들어 손쉽게 갈아끼워서 사용가능하도록 함

유연하고 동적인 코드 실현 가능!

LSP(Liskov Substitution Principle)

Subclassing을 하더라도 subtyping이 충족되어야 한다!!

상속 및 구현 할때 자손은 모든 부모의 기능을 수행할수있어야하며 부모 위치에 모두 대체될수있어야한다

즉, A가 B를 상속했을때 모든 B가 A로 상속되어서 B가쓰이는 모든 자리에 A로 대체될수있어야한다

상속에서 메서드를 재정의 할때 문법적으로 다음이 보장되어야 한다.

매개변수 반변성 매개변수 타입은 같거나 상위타입만 가능하다. (일반화만 가능)

반환타입 공변성 반환타입은 같거나 더 하위타입만 가능하다. (특수화만 가능)

자식은 부모보다 더 많은 예외를 발생시킬수없다.

논리적으로 다음을 보장해야한다.

상위 타입의 불변 조건은 상속시 유지되어야 한다.

메서드의 사전조건은 강화되지 않아야한다.

부모 메서드가 모든 수일때 동작했는데 자손은 양수일때만 동작하면 안된다.

메서드의 사후조건은 약화되지 않아야한다.

부모가 양수만 반환했는데 자손이 모든수를 반환해선 안된다.

LSP 문제 발생시 해결책

방법 1. 부모 자식 간이 아니라 형제 클래스로 모델링할 수 있음 방법 2. 상속으로 모델링하고 필요한 조처를 함 방법 3. 포함 관계를 활용(추천!)

ISP(Inteface Segregation Principle)

불필요하거나 빈 메서드를 정의하는 일이 없도록 해야한다.

클래스나 interface가 제공하는 메소드의 수는 최소화 단일 메소드 interface를 사용하면 ISP가 위배될 수 없음 하지만 항상 같이 제공되어야 하는 것까지 분리할 필요는 없음

예제

interface A {
 void f();
}

class X impelments A{
 void f(){
     ...
 }
}

class Y impelments A{
  void f(){
     ...
 }
}

다음과 같이 인터페이스 A를 구현하는 X와 Y가 있다.

앞으로 생성할 다른 클래스들이 g라는 메서드를 필요로 한다.

X는 g가 필요하고 Y는 g가 필요하지 않다.

어떻게 설계하는게 바람직한가?

방법 1. A에 g를 추가함. X는 자신에 맞게 g를 재정의하여 사용함. Y는 g를 "빈 메소드"로 재정의하여 문제가 없도록 함
 // LSP를 위반한다. Y는 g를 사용하지 않는것과 다름없다.
 // A라는 리모컨의 기능을 Y는 전부 활용하지 않는다. 가능한 척을 할 뿐이다.

방법 2. A를 상속하는 새 interface B를 정의하고 B에 g를 선언함. X는 interface B를 구현하도록 수정하고 자신에 맞게 g를 재정의하여 사용하고, Y는 수정 없이 사용함
 // ISP방식에 가장 적합한 방법 "중간클래스 느낌"
 // 공통리모컨으로 A를 사용할수있다.

 // A가 B를 상속받도록 변형하여 적용할수도있다. Y는 빈메서드를 정의해야하지만 전체적인 수정 횟수는 변형방식이 더 적다.

방법 3. 새 interface B를 정의하고 B에 g를 선언함. X는 interface B를 추가 구현하도록 수정하고 자신에 맞게 g를 재정의하여 사용함. Y는 수정 없이 사용함
 // 단점: X,Y을 사용할수있는 공통 리모컨이 존재하지 않는다.

DIP(DependencyInversion Principle)

DIP: 의존관계역전

의존을 하더라도 구체적인 클래스 대신에 상위 추상 타입이나 interface에 의존해야 한다

공통리모컨을 사용해야한다. 부모클래스 , abstact 부모클래스 , 인터페이스

ex) 생성패턴, 생성할 모든 종류의 제품이 같은 추상타입을 사용하여 DIP 만족함

의존관계는 적을수록 좋고 일방향이어야 한다.

DIP 예제)

class Person{
	private Pet pet; 

	public Person(Pet pet){
		setPet(pet);
	}

	public void setPet(Pet pet){
		this.pet = pet;
	}
};

//생성단계 TESTCODE
Person p = new Person(new Pet)//관계 주입 DI
  • 관계 주입을 이용한다. (가장 보편적인 방법)

[중요!]클래스를 정의할때 관계를 고정하는것이 아니라 객체 생성단계에서 관계를 맺도록 하는것 , 생성자나 Setter를 이용한다.

  • 동적으로 의존관계 설정이 가능
  • 다형성 이용 느슨한 결합

 

SOLID를 기반으로 다양한 ''설계패턴''이 존재한다.

GoF(Gang of Four)는 설계 패턴을 크게 다음과 같은 3가지 종류로 분류하였다.

생성(creational): 객체의 생성과 관련되어 있음 • 행위(behavioral): 객체와 객체 간 상호작용과 관련되어 있음 • 구조(structural): 객체 간에 정적인 구조적 관계와 관련되어 있음

패턴을 사용하는 이유

유연성: 코드의 유연성(확장 용이성) 확보 올바른 수준의 추상화 제공, 객체 간에 낮은 결합성 제공 재사용성: 코드의 재사용 가능성을 높여줌 개발자 간 의사소통 강화 가장 좋은 해결책의 사용 설계 패턴들을 널리 검증된 해결책임

소프트웨어 개발론

폭포수 모델 정의>분석>설계>구현>테스트/평가>배포>유지보수

반복적 개발 방법론

일련의 짧은 고정된 크기의 작은 프로젝트 단위로 전체 개발을 나누어 진행하는 방법 : 1 FIX 1 TEST

  • 애자일(agile) 소프트웨어 개발 선언

 

안티패턴

  • 리펙터링이 필요한 코드스멜

이해하기 힘든 이름

많고 복잡한 주석

코드 중복

긴 함수

큰 클래스(블랙홀 클래스)

너무 작은 클래스(데이터 클래스)

데이터 덩어리 : 항상 같이 등장하는 데이터가 있으면 이들을 묶어 데이터 클래스를 만들어 사용

긴 매개변수 목록

광역 데이터

양방향 의존관계

너무 많은 의존관계

체인 메서드 : a.getB.getC.fly()

LSP, ISP 문제: 부모 메서드가 자손에게 필요없는경우 -> 중간클래스나 빈메서드 재정의

너무 자주 사용되는 클래스: 사용관계 클래스를 포함관계로 만들기

 

728x90

'개발자 준비 > 디자인패턴 & 아키텍쳐' 카테고리의 다른 글

관찰자(Observer) 패턴  (0) 2021.12.29
전략패턴(Strategy Pattern)  (0) 2021.12.29
[번외] UML 이해하기  (0) 2021.12.29
OOP 개요  (0) 2021.12.29
디자인 패턴이란  (0) 2021.12.29