6 분 소요

개요

SOLID 원칙이란 객체지향언어로 설계를 할 때 지켜야할 5가지 원칙을 뜻한다.
SOLID 원칙을 지킨다면 코드의 유지보수와 프로젝트의 확장을 쉽게 할 수 있다.
SOLID라는 이름은 단일 책임 원칙 (Single responsibility principle), 개방-폐쇄 원칙 (Open/closed principle), 리스코프 치환 원칙 (Liskov substitution principle), 인터페이스 분리 원칙 (Interface segregation principle), 의존관계 역전 원칙 (Dependency inversion principle), 이렇게 5가지 원칙의 앞글자를 따서 지은 이름이다.

1. 단일 책임 원칙 (Single responsibility principle)

  • 한 클래스는 하나의 책임만 가져야한다는 의미 (한 가지 일만 수행하도록 설계해야함)
  • 한 클래스 내에서 다른 성격의 역할을 수행하는 메서드들은 다른 클래스로 분리
    ex. 상품을 선택하는 일을 하는 메서드는 repository class, 상품을 찾는 일을 하는 메서드는 console class로 분리
  • 같은 성격의 역할을 수행하는 메서드들을 다른 클래스로 분리할 필요는 없음
    ex. repository class에서 전체상품을 선택하는 class와 상품번호/이름으로 선택하는 class를 분리하는 것은 과도함
    Untitled1

    //나쁜 예시1
    class Repository { //상품선택 및 찾기 기능을 한 클래스에서 모두 수행
    	public void findByProductNo()
      public void findByProductName()
      public void findAllProducts()
    	public void selectByProductNo()
      public void selectByProductName()
      public void selectAllProducts()
    }
    
    //나쁜 예시2
    class RepositorySelectAll { //모든상품선택
      public void selectAllProducts()
    }
    class RepositorySelectByProduct { //상품번호/이름으로 선택
    	public void selectByProductNo()
      public void selectByProductName()
    }
    class ConsoleFindAll { //모든상품찾기
      public void findAllProducts()
    }
    class ConsoleFindByProduct { //상품번호/이름으로 찾기
    	public void findByProductNo()
      public void findByProductName()
    }
    
    //좋은 예시
    class Repository { //상품선택 기능 수행
    	public void selectByProductNo()
      public void selectByProductName()
      public void selectAllProducts()
    }
    class Console { //상품찾기 기능 수행
    	public void findByProductNo()
      public void findByProductName()
      public void findAllProducts()
    }
    
    

2. 개방-폐쇄 원칙 (Open/closed principle)

  • 소프트웨어 요소는 확장에는 열려있고, 변경에는 닫혀있어야한다.
  • 확장에 대해 열려있다 : 프로그램에 대한 요구사항이 변경될 때, 변경에 맞게 새로운 동작을 추가하여 모듈을 확장할 수 있다.
  • 변경에 대해 닫혀있다 : 모듈의 소스코드 or 라이브러리 등을 건드리지 않고도 모듈의 기능을 확장 or 변경할 수 있다. (하나의 변경사항이 다른 코드에도 영향을 끼치지 않게 함)
  • interface를 이용한 추상화를 적극 활용
    Untitled2

        //나쁜 예시
        class ListRepository {
        	public void selectByProductNoInList()
        	public void selectByProductNameInList()
        	public void selectAllProductsInList()
        }
        class FileRepository {
        	public void selectByProductNoInFile()
        	public void selectByProductNameInFile()
        	public void selectAllProductsInFile()
        }
        class Main { //실행부
        	public static void main(String[] args) {
        	//FileRepository를 사용할 경우, 메서드 이름도 일일히 다 수정해줘야함
        		ListRepository repository = new ListRepository();
        		repository.selectByProductNoInList();
        		repository.selectByProductNameInList();
        		repository.selectAllProductsInList();
        	}
        }
    
        //좋은 예시
        interface Repository {
        	public void selectByProductNo;
        	public void selectByProductName;
        	public void selectAllProducts;
        }
        class ListRepository implements Repository {
        	@Override
        	public void selectByProductNo()
        	public void selectByProductName()
        	public void selectAllProducts()
        }
        class FileRepository implements Repository {
          @Override
          public void selectByProductNo()
        	public void selectByProductName()
        	public void selectAllProducts()
        }
        class Main { //실행부
        	public static void main(String[] args) {
        	//FileRepository를 사용할 경우, 객체선언할때를 제외하고는 코드를 수정할 필요 없음
        		Repository repository = new ListRepository();
        		repository.selectByProductNo();
        		repository.selectByProductName();
        		repository.selectAllProducts();
        	}
        }
    

3. 리스코프 치환 원칙 (Liskov substitution principle)

  • 해당 객체를 사용하는 클라이언트는 상위 타입이 하위 타입으로 변경되어도, 차이점을 인식하지 못한 채 상위 타입의 퍼블릭 인터페이스를 통해 서브 클래스를 사용할 수 있어야 함
  • 하위 클래스는 상위 타입을 완전히 대체해서 사용할 수 있어야함

    Untitled3

        //나쁜 예시
        public class Main { // Main 클래스 (실행)
        public static void main(String[] args) {
        Rectangle r1 = new Rectangle(2,2);
        Rectangle r2 = new Rhombus(2,2);
        System.out.println(r1.calcArea()); // 결과 : 4 (Rectangle class의 calArea() method로 계산함 - 길이 _ 높이)
        System.out.println(r2.calcArea()); // 결과 : 4 (부모 class의 calArea() method로 계산함 - 길이 _ 높이, 마름모의 넓이는 길이 \* 높이 / 2 이므로 틀린 계산)
        // Rhombus (자식) class는 Rectangle (부모) class를 완전히 대체하지 못함
        }
        }
    
        class Rectangle { // 사각형 (부모클래스)
        //field
        int width;
        int height;
        //constructor
        public Rectangle (int width, int height) {
        this.width = width;
        this.height = height;
        }
        //사각형의 면적 값 계산
        public int calcArea () {
        return width * height;
        }
        }
        class Rhombus extends Rectangle { // 마름모 (자식클래스)
        //constructor
        public Rhombus(int width, int height) {
        super(width, height);
        }
        }
    
        //좋은 예시
        public class Main { // Main 클래스 (실행)
        public static void main(String[] args) {
        Rectangle r1 = new Rectangle(2,2);
        Rectangle r2 = new Rhombus(2,2);
        System.out.println(r1.calcArea()); // 결과 : 4 (Rectangle class의 calArea() method로 계산함 - 길이 _ 높이)
        System.out.println(r2.calcArea()); // 결과 : 2 (overriding한 calArea() method로 계산함 - 길이 _ 높이 / 2)
        // Rhombus (자식) class는 Rectangle (부모) class를 완전히 대체
        }
        }
    
        class Rectangle { // 사각형 (부모클래스)
        //field
        int width;
        int height;
        //constructor
        public Rectangle (int width, int height) {
        this.width = width;
        this.height = height;
        }
        //사각형의 면적 값 계산
        public int calcArea () {
        return width * height;
        }
        }
        class Rhombus extends Rectangle { // 마름모 (자식클래스)
        //constructor
        public Rhombus(int width, int height) {
        super(width, height);
        }
        //마름모의 면적 값 계산
        @Override
        public int calcArea () {
        return super.width * super.height / 2;
        }
        }
    

4. 인터페이스 분리 원칙 (Interface segregation principle)

  • 특정 클라이언트를 위한 범용 인터페이스 하나보다 여러개의 인터페이스가 낫다.
  • 하나의 인터페이스를 상속받는 여러개의 클래스가 있을 때, 각각의 클래스들에게 필요한 메서드들이 서로 다를 수 밖에 없다.
  • 공통적으로 필요한 메서드들을 상위 인터페이스로 만듦
  • 각각의 클래스들에게 필요한 메서드들을 하위인터페이스들로 분리시킴

Untitled4

  //나쁜 예시
  public interface Shape { // 도형 인터페이스
  public void calcCentroid(); // 무게중심 계산 (평면/입체도형 공통)
  public void calcArea(); // 면적 계산 (평면도형에서만 사용)
  public void calcVolume(); // 부피 계산 (입체도형에서만 사용)
  }

  public class Triangle implements Shape { // 삼각형 클래스 (평면도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //면적 계산 메서드 구현
  }
  }
  public class Rectangle implements Shape { // 사각형 클래스 (평면도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //면적 계산 메서드 구현
  }
  }
  public class Cylinder implements Shape { // 원기둥 클래스 (입체도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //부피 계산 메서드 구현
  }
  }
  public class Cone implements Shape { // 원뿔 클래스 (입체도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //부피 계산 메서드 구현
  }
  }

  //좋은 예시
  public interface Shape { // 도형 인터페이스
  public void calcCentroid(); // 무게중심 계산 (평면/입체도형 공통)
  }
  public interface PlaneFigure extends Shape { // 평면도형 인터페이스
  public void calcArea(); // 면적 계산 (평면도형에서만 사용)
  }
  public interface SolidFigure extends Shape { // 입체도형 인터페이스
  public void calcVolume(); // 부피 계산 (입체도형에서만 사용)
  }

  public class Triangle implements PlaneFigure { // 삼각형 클래스 (평면도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //면적 계산 메서드 구현
  }
  }
  public class Rectangle implements PlaneFigure { // 사각형 클래스 (평면도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //면적 계산 메서드 구현
  }
  }
  public class Cylinder implements SolidFigure { // 원기둥 클래스 (입체도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //부피 계산 메서드 구현
  }
  }
  public class Cone implements SolidFigure { // 원뿔 클래스 (입체도형)
  @Override
  public void calcCentroid() {
  //무게중심 계산 메서드 구현
  }
  @Override
  public void calcArea() {
  //부피 계산 메서드 구현
  }
  }

5. 의존관계 역전 원칙 (Dependency inversion principle)

  • 구체화가 추상화에 의존해야하지, 추상화가 구체화에 의존해서는 안된다.
  • 상위 레벨의 모듈이 하위 레벨의 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야한다.
    Untitled5

        //예시
    
        interface Repository { // 인터페이스를 통한 추상화
        	public void selectByProductNo;
        	public void selectByProductName;
        	public void selectAllProducts;
        }
        class ListRepository implements Repository { // List 자료구조를 이용하는 repository (low level module)
        	@Override
        	public void selectByProductNo()
        	public void selectByProductName()
        	public void selectAllProducts()
        }
        class FileRepository implements Repository { // 외부 파일저장소를 이용하는 repository (low level module)
          @Override
          public void selectByProductNo()
        	public void selectByProductName()
        	public void selectAllProducts()
        }
    
        class Console { // repository에서 상품 찾기 기능 수행 (high level module)
          Repository repository = new ListRepository(); // FileRepository도 선택 가능
          public void findByProductNo() {
        		repository.selectByProductNo();
        		System.out.println(상품정보출력);
        	}
          public void findByProductName() {
        		repository.selectByProductName();
        		System.out.println(상품정보출력);
        	}
          public void findAllProducts() {
        		repository.selectAllProducts();
        		System.out.println(상품정보출력);
        	}
        }
    

참고자료

첫 번째 글입니다 가장 최근 글입니다

댓글남기기