본문으로 바로가기

객체 지향 설계 SOLID 5원칙

category 디자인패턴 2022. 4. 8. 00:17

S: Single Responsibility Principle , 단일 책임 원칙

: 하나의 객체(클래스)는 하나의 책임만을 가져야 한다. 

class Student {
	public void getCourses();
    public void addCourse();
    public void save();
    public Student load();
    public void printOnReportCart();
    ...
   }

학생 이라는 클래스가 있다고 가정해 본다. 

 

학생 클래스는 수강 과목을 추가하거나 조회할 수 있고 

정보를 저장하거나 읽는 작업을 처리하며

성적표, 출석부에 출력하는 일을 할 수 있다고 한다. 

 

하지만 이 클래스는 현재 너무나 많은 책임을 가지고 있어 단일 책임에 위배된다.

 

Student 클래스가 가장 잘 할 수 있는 일은 수강신청 기능이다. 

다른 기능은 다른 클래스에서도 수행할 수 있다.

 

책임이 많아질 수록 다른 코드끼리 강한 결합을 갖게 될 수 있다. ( 수강신청을 하면서 학생정보를 업데이트 하는 등 )

또한 변경될 가능성이 많아진다. ( 출석부의 출력 형식이 변경된다면 ? 데이터 스키마에 변화가 생긴다면? )

 

클래스 내 변경사항이 많아진다면 모든 코드를 다시 테스트 해야 한다. 

 

학생 클래스에 하나의 책임을 부여하고 변경될 수 있는 상황을 하나만 만들어야 한다.

 

class Student {
	StudentDao studentDao;
    AttendanceBook attendanceBook;
    ReportCard reportCard;
    ...
 }
 
 
 class StudentDao{};
 class AttendanceBook{};
 class ReportCard{};

위 코드 처럼 Student의 책임을 분리한다면 변경에 대한 영향을 최소화 할 수 있다.

 

 

산탄총 수술

위의 예시는 하나의 클래스가 여러 책임을 갖는 예제를 말한 것이다.

하지만 하나의 책임이 여러 클래스에 분포되어 있는경우 SRP에 의해 설계를 변경해야 한다.

 

산탄총 수술이란 하나의 총알에 여러개의 산탄이 들어 있어 여러 발의 총알을 맞은 것과 같은 상태가 된다. 이때 의사는 환부 (여러 클래스)를 모두 찾아야 한다.

 

마찬가지로 하나의 책임이 여러 클래스에 분포 되어 있는 경우, 변경이 일어날 시 모든 클래스를 뒤져야 하는 참사가 일어난다. 

 

대표적으로 로깅, 트랜잭션과 같은 횡단 관심사가 있으며 이 문제를 관심 지향 프로그래밍으로 해결할 수 있다.

(Aspect, Join Point, Advice... )

 

 


O: Open Close Principle, 개방-폐쇄 원칙

: "코드의 변경에는 폐쇄적이어야 하고 확장에는 개방되어야 한다."   OCP의 주요 개념은 추상화와 다형성이다.

새로운 기능이 추가될 경우 기존 코드 수정이아닌 새로운 클래스를 추가하여 기능을 확장하도록 해야한다. 

 

간단한 예시)

class SomeClass {
	public void saveFile(InputFile excelfile) {
    
    	data = excelfile.read();
    	save(data);
    }
}

client에서 파일을 넘기면 해당 파일을 읽어서 저장하는 클래스가 있다고 가정한다. 

처음에는 Excel 파일만 허용했으며 data 에 excelfile 들을 정리해서 저장하고 save 로 넘겨준다. 

 

하지만 이후에 xml 타입의 파일이 추가되었다면 ?

class SomeClass {
	public void saveFile(String fileType, File file) {
    	if (fileType === excel) {
        ...
        
        } else if (fileType === xml) {
        ...
        }
        
        
        save(data)...
    }
 }

 

xml에 맞는 데이터를 읽어줭야하고 fileType 에 따라 분기처리를 해줘야한다. 

이후에 파일이 더 추가된다면 더 많은 if 문이 추가될 것이고 그때마다 코드의 수정이 이루어 진다. 

 

 

interface DataFile {
	byte readFile();
}

class ExcelFile implements DataFile{
	@Override
	public readFile() {
    
 	}
}


class XmlFile implements DataFile {
	@Override
    public readFile() {
    
    }
}


class SomeClass {
	public void saveFile(DataFile file) {
    	data = file.read();
        save(data);
    }
}

 

DataFile이라는 인터페이스를 만들어 추상화한다면 SomeClass에서 if-else 구문이 삭제가 된다.

또한 이후에 다른 파일타입이 추가되었을 때도 SomeClass에서의 코드의 수정은 일어나지 않는다. 

 

새로운 타입을 넣고싶다면 DataFile 인터페이스를 상속받아 readFile을 로직에 맞게 구현할 뿐이다. (다형성)

 

 

이렇게 개방폐쇄 원칙은 상속과 인터페이스를 통해 지켜질 수 있다. 

 

따라서 쉽게 변할 수 있는 대상에 대해 확장이 가능하도록 인터페이스를 사용해야한다. 이 때, 기존 코드에 대해서는 수정할 사항이 없고 interface를 implements 하여 확장 시켜 나갈 수 있다. 

 

 

 

Spring에서 대표적으로 Database를 사용할 때 보면 

예를 들어 처음에는 Mysql 사용했다고 치자, 하지만 이후에 모종의 이유로 Database를 Oracle로 바꿔야하는 이유가 생겨버린다면 ?

 

개발자는 이에 대해 다시 connection 코드를 설정하는 등 의 필요가 없다. 

단지 properties에 mysql -> oracle 로 변경해주면 알아서 connection 이 교체된다. 

 

 

 

 

 


 

L: The Liskov Substitution Principle, 리스코프 치환 원칙

: 자식 클래스는 부모 클래스를 대체할 수 있다. 부모 클래스에 자식 클래스를 넣어도 부모와 동일하게 작동해야 한다. 

 

즉 자식 클래스가 부모 클래스를 상속받아 오버라이딩한다면 LSP 원칙에 어긋나게 되는 것이다. 

 

Class Bag {
	int price;
    
   	public void printPrice(){
  		System.out.println(this.price);
    }
    
 }
 
 
 Class DiscountBag extends Bag {
 	
    public void setDiscountPrice(int discount) {
    	this.price = // 할인된 가격
    }
 
 }

 

위 Bag과 DiscountBag은 LSP를 만족한다고 할 수 있다.

DiscountBag은 부모인 Bag 을 상속 받아 오버라이딩 하지 않고 그대로 사용하고 있어 부모를 대체할 수 있는 자식 클래스가 된다.

 

 

 


 

I: Interface Segregation Principle, 인터페이스 분리 원칙 

: 하나의 클래스는 자신이 사용하지 않는 인터페이스에 대해 구현하지 말아야 한다. 

 

게시판을 구현한다고 가정해 보자. 흔히 게시판을 구현할 때 일반 User의 권한과 Admin의 권한을 가진 게시판으로 나눌 수 있다. 

사용하는 Client에 따라 다른 기능을 사용할 것이며 어떤 Client는 User 혹은 Admin중 하나의 기능만 사용할 수도 있다.

 

 

 

이는 ISP원칙에 어긋나며 게시판 클래스는 User용 게시판과 Admin용 클래스로 나누어 져야 한다.

 

하나의 인터페이스 보다는 여러 개의 인터페이스로 나누어 구현하는게 낫다. 

 

인터페이스를 나누면 대체 가능성이 커지고 변화의 가능성이 줄어들게 된다. 

 

 


 

 

D: Dependency Inversion Principle, 의존 관계 역전 원칙 

: 의존 관계를 맺을 때 변하기 어려운 객체와 관계를 맺어야 한다. 구체적인 클래스가 아닌 인터페이스와 의존 관계를 맺어야 한다. 

 

변화하기 쉬운 것보다 변하지 않는 것과 의존관계를 맺어야 한다. 

구체적인 class 보다 interface 혹은 abstract class 를 통해 코드의 변화에 적은 영향을 받게 된다. 

 

 

'디자인패턴' 카테고리의 다른 글

[디자인패턴] 템플릿 메소드 패턴  (0) 2023.01.02
싱글톤 패턴 (Singleton)  (0) 2022.05.19
스트래티지 패턴 (Strategy Pattern)  (0) 2022.03.29