본문 바로가기
정보처리기사/필기

[정보처리기사 필기 요약] 객체지향 설계원칙 (SOLID)

by 채연2 2021. 3. 2.
구분 내용
단일 책임 원칙
(SRP, Single Responsibility Principle)
- 객체는 단 하나의 책임만을 가져야 함
- 어떤 클래스를 변경해야 하는 이유는 단 한 가지여야 함(책임 = 변경사유)
- 같은 이유로 변화하는 것 끼리 묶고, 다른 이유로 변화하는 것 끼리는 분리
- 산탄총 수술 : 하나의 책임이 여러 클래스로 분산되어 있는 경우 발생 가능
개방 폐쇄 원칙
(OCP, Open-Closed Principle)
- 기존 코드 변경하지 않으면서 기능 추가할 수 있도록 설계 되어야 함
- SW 개체(Classes, Modules, Functions 등) 확장에는 열려있고 수정 시에는 닫혀있어야 함 --> 추상화
- 클래스 변경하지 않고도 그 클래스 둘러싼 환경 변경할 수 있는 설계 되어야 함
리스코프 치환의 원칙
(LSP, Liskov Substitution Principle)
- 부모 클래스와 자식 클래스 사이 행위가 일관성이 있어야 함
- 일반화 관계(is a Kind of 관계)에 대한 것으로 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위 수행 가능해야 함
- 부모 클래스 인스턴스를 자식 클래스 인스턴스로 대체해도 P/G 의미는 변화되지 않음
- 서브타입은 언제나 자신 기반 타입으로 교체 가능해야 한다는 원칙
인터페이스 분리의 원칙
(ISP, Interface Segregation Principle)
- 인터페이스를 클라이언트에 특화되도록 분리하는 설계 원칙
- 하나의 일반적인 인터페이스보다 구체적인 여러 개 인터페이스가 나옴
- 클라이언트가 사용하지 않는 메서드에 의존하지 않아야 함
의존성 역전의 원칙
(DIP, Dependency Inversion Principle)
- 의존 관계 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어렵거나 거의 변화가 없는 것에 의존하라는 것
- 추상화된 것에 의존하게 만들고 구체 클래스에 의존하도록 만들지 않도록 함

 

 

* SRP 예

//SRP 위배
class Book {
	public String getTitle() {
    	return "A Great Book";
    }
    
    public String getAuthor() {
    	return "John Doe";
    }
    
    public void turnPage() {
    	//pointer to netxt page
    }
    
    public void printCurrentPage() {
    	Log.d("book", "current page content");
    }
}

//SRP 준수
class Book {
	public String getTitle() {
    	return "A Great Book";
    }
    
    public String getAuthor() {
    	return "John Doe";
    }
    
    public void turnPage() {
    	//pointer to netxt page
    }
    
    public void printCurrentPage() {
    	return "current page content";
    }
}

public interface Printer {
	public void printPage(int page);
}

class PlainTextPrinter implements Printer {
	public void printPage(int page) {
    	Log.d("book", "page : " + page);
    }
}

 

* OCP 예

// OCP 위배

▲ Client와 Server 모두 구체 클래스(concrete class)이기 때문. 만약 Client가 다른 서버를 사용해야 한다면 Client 클래스 자체를 변경해야 함.

 

//OCP 준수

▲ Client는 목적에 맞는 전략(서버) 선택 가능. ClientInterface는 추상 멤버 함수를 포함하는 추상 클래스

Client 클래스는 이 추상화를 사용하지만 런타임에 생성된 Client 클래스의 객체는 실제로는 파생 Server 클래스의 객체 사용. 이렇게 되면 Client 클래스 자체는 변경되지 않음.

또한, Client에 명시된 행위들은 ClientInterface의 새로운 서브타입을 생성하는 것으로써 확장되거나 수정될 수 있음.

 

 

* LSP 예

- 정사각형 클래스가 직사각형 클래스를 상속해버리면, 정사각형의 특징인 '네 변의 길이는 같다'와 그렇지 않은 직사각형의 차이로 인해 직사각형을 정사각형 클래스로 치환해서 사용할 때, 네 변의 길이에 대한 두 클래스 특징 차이 때문에 기존 P/G 오작동 가능성 있음.

public class Bag {
    private int price;
    
    public void setPrice(int price){
        this.price = price;
    }
    
    public int getPrice() {
        return price;
    }
}

//슈퍼 클래스에서 상속받은 메서드들이 서브 클래스에 오버라이드, 즉 재정의되지 않도록 해야 함
//DiscountedBag 클래스와 Bag 클래스의 상속 관계가 LSP를 위반하지 않음
public class DiscountedBag extends Bag{
    private double discountedRage = 0;
    
    public void setDiscounted(double discountedRate) {
        this.discountedRage = discountedRate;
    }
    
    public void applyDiscount(int price) {
        super.setPrice(price - (int)(discountedRage * price));
    }
}

 

 

* ISP 예

//ISP 위배
//맥북 2015년형에는 터치바가 없음 --> 클라이언트가 사용하지 않는 메서드에 의존하지 않아야 함
public interface MacPro {
  void display();
  void keyboard();
  void touch();
}

public class MacPro2016 implements MacPro {
  public void display() {
    Log.d("log", "mac pro2016 display");
  }

  public void keyboard() {
    Log.d("log", "mac pro2016 keyboard");
  }

  public void touch() {
    Log.d("log", "mac pro2016 touch bar");
  }
}

public class MacPro2015 implements MacPro {
  public void display() {
    Log.d("log", "mac pro2015 display");
  }

  public void keyboard() {
    Log.d("log", "mac pro2015 keyboard");
  }

  public void touch() {
    // ?? 
  }
}


//ISP 준수
//인터페이스를 각각 쪼개서 사용하는 인터페이스만 구현.
public interface MacPro {
  void display();
  void keyboard();
}

public interface MacProTouch {
  void touch();
}

public class MacPro2016 implements MacPro, MacProTouch {
  public void display() {
    Log.d("log", "mac pro2016 display");
  }

  public void keyboard() {
    Log.d("log", "mac pro2016 keyboard");
  }

  public void touch() {
    Log.d("log", "mac pro2016 touch bar");
  }
}

public class MacPro2015 implements MacPro {
  public void display() {
    Log.d("log", "mac pro2015 display");
  }

  public void keyboard() {
    Log.d("log", "mac pro2015 keyboard");
  }
}

 

 

* DIP 예

//DIP 위배
//고수준모듈인 Response 는 저수준 모듈인 JsonConverter 구현에 직접적으로 의존
public class Response {
  private JsonConverter jsonConverter = new JsonConverter();
  public String response()  {
    byte[] bytes = null; //파일은 생략
    return jsonConverter.convert(bytes);
  }
}

class JsonConverter {
  public String convert(byte[] bytes) {
    //json ...
    return "json";
  }
}


//DIP 준수
//생성자 주입(Constructor Injection) --> 의존자 인스턴스 생성됨과 동시에 Converter 생성됨
public class Response {
  private final Converter converter;

  public Response(Converter converter) {
    this.converter = converter;
  }
  
  public String response() {
    byte[] bytes = null; //파일은 생략
    return converter.convert(bytes);
  }

}

class XmlConverter implements Converter {
  @Override
  public String convert(byte[] bytes) {
    //xml ...
    return "xml";
  }
}
320x100

댓글