ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체지향 5원칙 : SOLID
    Java 2022. 4. 22. 23:25

    아주 유명한 객체지향 5원칙인 SOLID에 대해서 자세하게 알아보겠습니다.

     

    SOLID란?

    로버트 마틴이 2000년대 초에 명명한 객체 지향 프로그래밍의 다섯 가지 기본 원칙의 앞글자를 따서 SOLID라는 이름으로 소개한 것입니다.

     

    1. SRP(Single Reponsibility Principle)

    SOLID 원칙의 제일 앞글자인 S에 해당하는 원칙으로 이름 그대로의 의미를 가집니다.

     

    단일 책임 원칙

     

    즉, 하나의 클래스는 하나의 기능만 가진다는 의미입니다. 

     

    하나의 기능? 과연 하나의 기능을 어디까지의 범위로 볼 것인가는 모호합니다.

     

    따라서 변경이라는 키워드에 초점을 맞추어 변경사항이 있을때 다른 클래스들에게 미치는 영향이 적으면 SRP 원칙을 잘 따른 것으로 볼 수 있습니다.

     

    SRP 원칙을 적용하게 되면 다른 클래스들에게 미치는 연쇄작용을 줄일 수 있으며 응집도는 높이고 결합도는 낮출 수 있습니다.

     

    2. OCP(Open Close Principle)

    SOLID의 O에 해당하는 원칙으로 모든 구성요소(클래스, 모듈, 함수)는 확장에는 열려있고, 변경에는 닫혀 있어야 한다는 원칙입니다.

     

    개방 폐쇄 원칙

     

    처음에 이 말을 들으면 이게 무슨 말인가 싶지만 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 않으며 쉽게 확장이 가능하여 재사용할 수 있어야 한다는 뜻입니다.

     

    이것을 위해서는 추상화와 다형성을 잘 사용해야 합니다.

     

    List 인터페이스로 예시를 들어보겠습니다.

    public interface List<E> extends Collection<E>

    List 인터페이스의 구현체인 ArrayList로 0~9까지의 수를 저장하는 간단한 예시입니다.

            List<Integer> list = new ArrayList<>();
            for(int i=0; i<10; i++){
                list.add(i);
            }
            System.out.println(list);

    여기서 만약 ArrayList를 LinkedList로 변경하게 되면 아래의 코드도 수정해야 할까요?

    List<Integer> list = new LinkedList<>();

    간단하게 ArrayList부분을 LinkedList로만 변경하게 되면 둘다 List 인터페이스를 구현하고 있기 때문에 아래의 코드들은 수정이 발생하지 않습니다.

     

    이렇게 하면 해당 인터페이스를 구현하고 있는 어떠한 구현체를 만들더라도 코드 수정을 하지 않아도 됩니다.

     

    즉, 확장에는 열려있고, 변경에는 닫히게 됩니다.

     

    3. LSP(the Liskov Substitution Principle)

    SOLID의 L에 해당하는 원칙입니다.

     

    리스코브 치환의 원칙

     

    부모 클래스를 가리키는 포인터에 해당 클래스를 상속하는 자식 클래스를 할당하더라도 모든 기능이 정상적으로 작동해야 하며 자식 클래스의 상세 내용을 부모 클래스는 알 필요가 없다는 뜻입니다.

     

    즉, 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 합니다.

     

    사실 위의 예제와 비슷하게 받아들일 수 있습니다. (OCP가 만족하기 위해서는 LSP는 이미 만족되어야 합니다)

     

    이때 주의할점은 같은 메서드를 사용하더라도 부모 클래스의 기존 메서드의 의미를 해치지 않아야 한다는 것입니다.

     

    예를 들어 자동차 클래스의 Move 메서드를 예로 들어보겠습니다.

     

    부모 클래스의 Move 메서드는 Move+=1으로 앞으로 이동하는 메서드를 구현하고 있다고 가정해보겠습니다.

     

    이때 자식 클래스의 Move 메서드가 더 빨라서 Move+=2 라고 하는 경우는 괜찮지만 Move -= 1으로 뒤로 이동한다면 문제가 발생할 수 있습니다.

     

    만약 자동차 클래스가 뒤로가는경우도 지원한다면 문제가 되지 않지만 뒤로 가는 경우를 지원하지 않는다면 이는 문제가 되기 때문에 기존 메서드의 의미를 잘 파악해야 합니다.

     

    4. ISP(Interface Segregation Principle)

    SOLID의 I에 해당하는 원칙입니다.

     

    인터페이스 분리의 원칙

     

    하나의 큰 인터페이스를 상속 받기 보다는 인터페이스를 구체적으로 작은 단위들로 분리시키자는 의미입니다.

     

    SRP와 비슷한 느낌입니다.

     

    SRP는 클래스의 단일책임을 강조했다면 ISP는 인터페이스의 단일 책임을 강조합니다.

     

    복합기 클래스를 예제로 들어보겠습니다.

    복합기는 인쇄, 복사, 팩스 기능을 가지고 있다고 가정해 보겠습니다.

    public interface AllInOneDevice {
        void print();
    
        void copy();
    
        void fax();
    }

     

    위의 복합기 클래스를 구현하는 스마트머신 객체를 선언해 보겠습니다.

    public class SmartMachine implements AllInOneDevice {
        @Override
        public void print() {
            System.out.println("print");
        }
    
        @Override
        public void copy() {
            System.out.println("copy");
        }
    
        @Override
        public void fax() {
            System.out.println("fax");
        }
    }

     

    여기까지는 문제가 없어 보입니다.

     

    하지만 만약 인쇄 기능만 있으면 되는 인쇄기에 위의 인터페이스를 적용하게 되면 어떻게 될까요?

    public class PrinterMachine implements AllInOneDevice {
        @Override
        public void print() {
            System.out.println("print");
        }
    
        @Override
        public void copy() {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void fax() {
            throw new UnsupportedOperationException();
        }
    }

    copy 메서드와 fax 메서드는 구현할 필요가 없기 때문에 UnsupportedOperationException을 발생시킵니다.

     

    하지만 인터페이스만 알고있는 클라이언트는 printer에서 copy기능이 구현되어 있는지 아닌지 모르기 때문에 예상치 못한 오류를 만날 수 있습니다.

     

    따라서 3개의 인터페이스로 나누어 보겠습니다.

    public interface PrinterDevice {
        void print();
    }
    
    public interface CopyDevice {
        void copy();
    }
    
    public interface FaxDevice {
        void fax();
    }

     

    이제는 특정기능이 필요하면 특정 기능만 implements 해서 구현하면 됩니다!

     

    5. DIP(Dependency Inversion Principle)

    SOLID의 마지막 D에 해당하는 원칙입니다.

     

    의존성 역전의 원칙

     

    클래스 사이에서는 의존관계가 존재할 수 있습니다.

     

    이때 구현체에 의존하지 말고 추상화에 의존해야 한다라는 의미입니다.

     

    아까 OCP와 비슷하게 무슨 뜻인지 쉽게 와닿지 않을 수 있습니다.

     

    코드를 보면서 이해해 보겠습니다.

    public class AlarmService {
    
    	private A alarm;
    
    	public String beep() {
    		return alarm.beep();
    	}
    
    }
    
    /**
     * A사의 알람 서비스
     */
    public class A {
    	public String beep() {
    		return "beep!";
    	}
    }

    AlarmService는 A 클래스에 의존하고 있는 모습입니다.

     

    위의 코드에는 두 가지 문제가 있습니다.

     

    1. 테스트의 어려움

    위 코드에서 AlaramService만 온전히 테스트할 수 없습니다. 

    A가 완벽하게 동작해야만 AlarmService를 테스트할 수 있습니다.

     

    2. 확장 및 변경의 어려움

    만약 알람 서비스에 B사가 추가된다면 어떻게 될까요?

    아래와 같이 서비스 코드를 변경해야 합니다.

    /**
     * B사의 알림 서비스
     */
    public class B {
    	public String beep() {
    		return "beep";
    	}
    }
    
    /**
    * 서비스 코드
    */
    public class AlarmService {
    
    	private A alarmA;
     	private B alarmB;
    
    	public String beep(String company) {
    		if (company.equals("A")) {
    			return alarmA.beep();
    		}
    		return alarmB.beep();		
    	}
    }

    만약 여기에 C사 알림서비스가 추가된다면 또 서비스 코드를 변경해야 합니다.

     

    이를 해결하기 위해서는 추상화와 구현체로 분리하면 됩니다.

    추상화 : 알림

    public interface Alarm {
    	String beep();
    }

    구현체 : A사의 알림 서비스, B사의 알림 서비스

    /**
     * A사의 알람 서비스
     */
    public class A implements Alarm {
    	@Override
    	public String beep() {
    		return "beep!";
    	}
    }
    
    /**
     * B사의 알림 서비스
     */
    public class B implements Alarm {
    	@Override
    	public String beep() {
    		return "beep";
    	}
    }

     

    이렇게 되면 서비스 코드를 다음과 같이 변경할 수 있습니다.

    /**
    * 서비스 코드
    */
    public class AlarmService {
    
    	private Alarm alarm;
    
    	public AlarmService(Alarm alarm) {
    		this.alarm = alarm;
    	}
    
    	public String beep() {
    		return alarm.beep();
    	}
    }

    이제는 더 이상 알림 서비스가 추가된다고 코드를 변경하거나 추가하는 일이 없어집니다.

    또한 무조건 인터페이스 Alram에 의존하기 때문에 Alram을 Mock객체로 만들어 다양한 시나리오로 AlramService 기능을 온전히 테스트할 수 있다는 장점도 가져갈 수 있습니다.

     

    실행코드까지 모음

    /**
     * 서비스 코드
     */
    class A implements Alarm {
        @Override
        public String beep() {
            return "A beep!";
        }
    }
    
    class B implements Alarm {
        @Override
        public String beep() {
            return "B beep";
        }
    }
    
    class AlarmService {
    
        private Alarm alarm;
    
        public AlarmService(Alarm alarm) {
            this.alarm = alarm;
        }
    
        public String beep() {
            return alarm.beep();
        }
    }
    
    public class DIPTest {
        public static void main(String[] args) {
            AlarmService alarmService = new AlarmService(new B());
            System.out.println(alarmService.beep());
        }
    }

    아까 적었던 글을 그대로 가져와 보겠습니다.

     

    클래스 사이에서는 의존관계가 존재할 수 있습니다.

     

    이때 구현체에 의존하지 말고 추상화에 의존해야 한다라는 의미입니다.

     

    이제는 조금 와닿는 것 같습니다

     

     

     

    출처

    https://jaeyeong951.medium.com/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-5%EC%9B%90%EC%B9%99-solid-ac7d4d660f4d

     

    객체지향 5원칙 : SOLID

    디자인 패턴에 대해 공부하던 중 예전 소프트웨어 공학 때 배운 객체지향 5원칙(SOLID)에 대해 다시 정리해보기로 했습니다.

    jaeyeong951.medium.com

    https://bottom-to-top.tistory.com/27

     

    객체지향 설계 5원칙 SOLID

    객체지향설계 5원칙 SOLID의 이해와 예제 목표 SOLID에 대한 설명을 하는 글은 여러 블로그에 소개가 되어있습니다. 하지만 대부분의 글이 개념적인 설명을 위주로 하고 있을뿐더러, 너무 추상적이

    bottom-to-top.tistory.com

    https://shinsunyoung.tistory.com/82

     

    DIP(Dependency Inversion Principle)

    DIP에 대해 알아보도록 하겠습니다. DIP는 객체지향설계 5원칙(SOLID)에서 D에 해당하는 원칙이며, 아래와 같은 의미를 가지고 있습니다. 저수준 모듈이 고수준 모듈에 의존하게 되는 것 하지만 이

    shinsunyoung.tistory.com

     

    'Java' 카테고리의 다른 글

    전략 패턴이란?  (0) 2022.05.25
    Java는 왜 Pure OOP가 아닐까?  (0) 2022.05.10
    [Java] 빌더 패턴  (0) 2022.04.09
    [Java] 프록시 패턴이란?  (0) 2022.04.08
    [Java] 리플렉션이란?  (0) 2022.04.07

    댓글

Designed by Tistory.