티스토리 뷰

확장에는 열려 있어야 하고, 변경에는 닫혀야 한다.

 

말이 좀 어려운데, 풀어보면 다음과 같다.

기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정하지 않는다.

 

기능을 변경하면서 그 기능을 사용하는 코드는 바꾸면 안된다..? 이것이 가능한 얘기일까?

예시를 하나 들어보자.

 

public class FlowController {

	public void process(){
    	FileDataReader reader = new FileDataReader();
        byte[] data = reader.read();
        
        Encryptor encryptor = new Encryptor();
        byte[] encryptedData = encryptor.encrypt(data);
        
        FileDataWriter writer = new FileDataWriter();
        writer.write(encryptedData);
    }
}

FlowController는 파일로부터 데이터를 읽고, 데이터를 암호화하고, 그것을 쓴다.

 

여기서 만약 데이터를 파일이 아닌, 소켓으로부터 읽어온다면 코드는 어떻게 바뀔까?

public class FlowController {

	public void process(){
    	byte[] data = null;
    	if(useFile){
    		FileDataReader reader = new FileDataReader();
        	data = reader.read();
        }
        else{
        	SocketDataReader socketReader = new SocketDataReader();
            	data = socketReader.read();
        }
        
        Encryptor encryptor = new Encryptor();
        byte[] encryptedData = encryptor.encrypt(data);
        
        FileDataWriter writer = new FileDataWriter();
        writer.write(encryptedData);
    }
}

가장 쉽게 생각하면 file일 경우와 아닐 경우를 if-else 문으로 나누는 것이다. 쉽고 간편한 방법이지만 데이터를 읽어들이는 방식이 추가될 때마다 if-else 문이 늘어나고 코드는 점점 지저분하고 복잡해질 것이다. 결국 프로그램의 유연성은 떨어지고 유지 보수가 어려워진다. 

데이터를 읽는 방식이 다양해지더라도 flowController의 코드는 그대로 유지할 수 있는 방법이 없을까?

 

public interface ByteSource{
	public byte[] read();
}
public class FileDataReader implements ByteSource{
	public byte read(){
    	...
    }
}

public class SocketDataReader implements ByteSource{
	...
}

먼저 데이터를 읽어들이는 것을 추상화하자. ByteSource 인터페이스를 만들고,FileDataReader와 SocketDataReader가 이를 구현하도록 했다.

 

public class ByteSourceFactory{

	public ByteSource create(){
    	if(useFile())
        	return new FileDataReader();
        else
        	return new SocketDataReader();
    }
    
    private boolean useFile(){
    	...
    }
    
    private static ByteSourceFactory instance = new ByteSourceFactory();
    public static ByteSourceFactory getInstance(){
    	return instance;
    }
    
    ...
}

그리고 적절한 구현체를 선택하는 객체를 따로 만들었다. ByteSourceFactory을 이용해 요구사항에 맞게 ByteSource의 type을 결정할 수 있다. 즉, File로 데이터를 읽을지, Socket으로 읽을지 변경하고 싶다면 이 Factory만 수정하면 된다.

(참고) 이 ByteSourceFactory를 대신할 수 있는 방법으로 생성자를 통해 적절한 ByteSource를 전달받는 방법이 있는데, 이것이 바로 Dependency Injection이다.

 

public class FlowController {

	public void process(){
    	/*
    	byte[] data = null;
    	if(useFile){
    		FileDataReader reader = new FileDataReader();
        	data = reader.read();
        }
        else{
        	SocketDataReader socketReader = new SocketDataReader();
            	data = socketReader.read();
        }
        */
        ByteSource source = ByteSourceFactory.getInstance().create();
        byte[] data = source.read();
        
        Encryptor encryptor = new Encryptor();
        byte[] encryptedData = encryptor.encrypt(data);
        
        FileDataWriter writer = new FileDataWriter();
        writer.write(encryptedData);
    }
}

마지막으로 FlowController를 보자. if-else문이 사라지고 데이터를 읽어들이는 부분이 간결해졌다.

이제 데이터를 파일로 받든, 소켓으로 받든, FlowController의 코드는 손대지 않아도 된다.

이것이 바로 OCP이다. 여기서 데이터를 읽는 방식으로 HTTP를 추가한다고 가정해보자.(기능 추가) 그래도 이 기능을 사용하는 FlowController의 코드는 바뀌지 않는다. OCP의 정의대로, 기능을 변경하거나 확장하면서도 그 기능을 사용하는 코드는 수정하지 않는 것이다.

 

 

(참고) OCP가 위반되었을 때 나타나는 증상

- instanceof 와 같은 타입 확인 연산자로 분기 처리됨.

- 유사한 if-else 블록이 존재.

 

 

요컨대, 개방 폐쇄 원칙은 변화가 예상되는 곳을 추상화해서 변경의 유연함을 얻게 해준다. 만약 기능이 변해서 코드를 바꿔야한다면, 변화와 관련된 구현을 추상화해서 OCP 원칙에 맞게 수정할 수 있는지 확인하자.



코드 출처, 참고 도서 : 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴, 최범균 지음, 인투북스

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함