객체지향 설계 5대 원칙 SOLID - shinminsoo118/solid_5 GitHub Wiki

1) SRP - 단일책임원칙

  • 단일 클래스는 단 한개의 책임을 가져야 한다. (클래스를 변경하는 이유는 단 한개여야 한다.)

before
public class UserSettingService {

  public void changeEmail(User user) {
    if(checkAccess(user)) {
       // Grant option to change
    }
  }
  
  public boolean checkAccess(User user) {
    // Verify if the user is valid.
  }
  
}

after
public class UserSettingService {

  public void changeEmail(User user) {
    if(SecurityService.checkAccess(user)) {
       // Grant option to change
    }
  }
  
}

public class SecurityService {

  public static boolean checkAccess(User user) {
    // Check the access.
  }
  
}

2. OCP - 개방폐쇄원칙

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

before
public class Rectangle {
    public double Width { ... }
    public double Height { ... }
}

public class AreaCalculator {
    public double Area(object[] shapes) {
        double area = 0;
        
        foreach (shape instanceof shapes) {
            if (shape instanceof Rectangle) {
                Rectangle rectangle = (Rectangle) shape;
                area += rectangle.Width * rectangle.Height;
            }
            else {
                Circle circle = (Circle)shape;
                area += circle.Radius * circle.Radius * Math.PI;
            }
        }
    
        return area;
    }
}

after
public interface class Shape {
    double Area();
}


public class Rectangle implements Shape {
    public double Width { ... }
    public double Height { ... }
    
    @Override
    public double Area() {
        return Width*Height;
    }
}

public class Circle implements Shape {
    public double Radius { ... }
    
    @Override
    public double Area() {
        return Radius * Radius * Math.PI;
    }
}


public double Area(Shape[] shapes) {
    double area = 0;
    foreach (var shape in shapes) {
        area += shape.Area();
    }

    return area;
}

3. LSP - 리스코브 치환원칙

  • 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

before
public class Coupon {
    public int calcuateDiscountAmount(Item item) {
        if (item instanceof SpecialItem) // LSP 위반
            return 0;
        
        return item.getPrice() * discountRate;
    }
}

after
public class Item {
    public boolean isDiscountAvailable() {
        return true;
    }
}

public class SpecialItem extends Item {
    // 하위 타입에서 알맞게 오버라이딩
    @Override
    public boolean isDiscountAvailable() {
        return false;
    }
}

public class Coupon {
    public int calcuateDiscountAmount(Item item) {
        if (!item.isDiscountAvailable()) // instanceof 연산자 제거
            return 0;
        
        return item.getPrice() * discountRate;
    }
}

4. ISP - 인터페이스 분리원칙

  • 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.

before
// 하나의 인터페이스를 모든 클라이언트가 구현하고 있음

public interface ArticleService {
    void list();
    void write();
    void delete();
}

public class UiList implements ArticleService {
    @Override
    public void list() {}
    
    @Override
    public void write() {}
    
    @Override
    public void delete() {}
}

public class UiWist implements ArticleService {
    @Override
    public void list() {}
    
    @Override
    public void write() {}
    
    @Override
    public void delete() {}
    
}

public class UiDist implements ArticleService {
    @Override
    public void list() {}
    
    @Override
    public void write() {}
    
    @Override
    public void delete() {}
    
}

after
// 각각의 클라이언트별로 Interface를 구분

public interface ArticleListService {
    void list();
}

public interface ArticleWriteService {
    void Write();
}

public interface ArticleDeleteService {
    void Delete();
}

public class UiList implements ArticleListService {
    @Override
    public void list() {}
}

public class UiWist implements ArticleWriteService {
    @Override
    public void write() {}
}

public class UiDist implements ArticleDeleteService {
    @Override
    public void delete() {}
}

5. DIP - 의존역전원칙

  • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
  • 고수준모듈(어떤 의미있는 단일 기능을 제공하는 모듈)
  1. 바이트 데이터를 읽어와 암호화하고 결과 바이트 데이터를 쓴다.
  • 저수준모듈(고수준 모듈의 기능을 구현하기 위해 필요한 개별기능. 좀 더 작은모듈)
  1. 파일에서 바이트 데이터를 읽어온다
  2. AES 알고리즘으로 암호화 한다.
  3. 파일에 바이트 데이터를 쓴다.
  • key point 중간에 interface와 같은 추상화를 고수준 모듈과 저수준 모듈이 모두 추상 타입에 의존하게 만든다.

before
// "Low level Module" Mechanism equivilant
public class Logger {
    public void logInformation(String logInfo) {
        System.out.println(logInfo);
    }
}

// "High level module" Policy equivalent.
public class Foo {
    // direct dependency of a low level module.
    private Logger logger = new Logger();

    public void doStuff() {
        logger.logInformation("Something important.");
    }
}

after
public interface ILogger {
    void logInformation(String logInfo);
}

public class Logger implements ILogger {
    @Override
    public void logInformation(string logInfo) {
        System.out.println(logInfo);
    }
}

public class Foo {
    private ILogger logger;
    
    public void setLoggerImpl(ILogger loggerImpl) {
        this.logger = loggerImpl;
    }

    public void doStuff() {
        logger.logInformation("Something important.");
    }
}

// Usage
Foo foo = new Foo();
ILogger logger = new Logger();
foo.setLoggerImpl(logger);
foo.doStuff();