언제쯤, 자연스럽게 코드에 반영할 수 있을까..
학부 시절 처음으로 SOLID 원칙에 대해 배우게 되면서 큰 흥미를 느꼈다. 그 이후로 여러 기회를 통해 SOLID 원칙을 공부하고 학습해 보았지만, 이를 실제 코드에 자연스럽게 적용하는 것은 쉽지 않았다.
하지만 반복을 통해, 체득하다 보니 정확히 정의를 뱉어낼 수는 없더라도 코드 작성 중에 어렴풋이 아 이런 식으로 짜야 하는데?라는 생각이 들게 되었다.
오늘은 다시 한번 학습을 하는 기념으로 코드와 함께 정리를 해보려고 한다.
1. SRP (Single Responsibility Principle) 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다.
하나의 모듈은 오직 하나의 액터에 대해서만 책임져야 한다.
SRP를 지키지 않은 코드
<java />
// 사용자 정보를 관리하는 클래스
public class UserX {
private int id
private String name;
private String email;
private String pw;
private List<Order> orders;// 주문내역 DB
public UserX(String name, String email, String pw) {
this.name = name;
this.email = email;
this.pw = pw;
this.orders = new ArrayList<Order>();
}
/*
주문 내역
*/
public void saveOrder(Order order) {
orders.add(order);
}
public Order getOrder(int orderId) {
for(Order order : orders) {
if(order.getId() == orderId)
return order;
}
return null;
}
public List<Order> getOrders() {
return orders;
}
}
public class Order {
private int id;
private int userId;
private String storeName;
private Date orderDate;
private List<String> menus;
public int getId() {
return id;
}
public int getUserId() {
return userId;
}
public Order(List<String> menus, Date orderDate, String storeName, int userId) {
this.menus = menus;
this.orderDate = orderDate;
this.storeName = storeName;
this.userId = userId;
}
}
SRP를 지킨 코드
<java />
// 사용자 정보를 관리하는 클래스
public class User {
private int id;
private String name;
private String email;
private String pw;
public User(String name, String email, String pw) {
this.name = name;
this.email = email;
this.pw = pw;
}
}
public class OrderService {
private List<Order> orders; // 주문내역 DB
public OrderService() {
this.orders = new ArrayList<>();
}
public void saveOrder(Order order) {
orders.add(order);
}
public Order getOrderById(int orderId) {
for (Order order : orders) {
if (order.getId() == orderId) {
return order;
}
}
return null;
}
// 사용자별 주문 내역 가져오기
public List<Order> getOrdersByUserId(int userId) {
List<Order> userOrders = new ArrayList<>();
for (Order order : orders) {
if (order.getUserId() == userId) {
userOrders.add(order);
}
}
return userOrders;
}
}
2. OCP (Open/Closed Principle) 개방-폐쇄 원칙
소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다.
기능을 추가할 때는 추가하는 부분 추가만 해야 하며, 기존 코드를 변경하면 안된다.
OCP를 지키지 않은 코드
<java />
// Car 클래스
public class Car {
String type;
public Car(String type) {
this.type = type;
}
public void charge(){
if(type.equals("GasCar")){
System.out.println("기름을 주유합니다.");
}
else if(type.equals("ElectricCar")) {
System.out.println("전기차를 충전합니다.");
}
// 다른 차를 추가하려면 아래와 같이 추가해줘야 함
else if(type.equals("HydrogenCar")){
System.out.println("수소차를 충전합니다.");
}
}
}
// Main
public class Main {
public static void main(String[] args) {
Car gasCar = new Car("GasCar");
Car electricCar = new Car("ElectricCar");
Car hydrogenCar = new Car("HydrogenCar"); // 수소차 추가
gasCar.charge();
electricCar.charge();
hydrogenCar.charge(); // 수소차 추가
}
}
OCP를 지킨 코드
<java />
// Car 인터페이스
public interface Car {
void charge();
}
// ElectricCar 클래스
public class ElectricCar implements Car{
@Override
public void charge() {
System.out.println("전기차를 충전합니다.");
}
}
// GasCar 클래스
public class GasCar implements Car{
@Override
public void charge() {
System.out.println("기름을 주유합니다.");
}
}
// HydrogenCar 클래스 // 다른 차 추가
public class HydrogenCar implements Car{
@Override
public void charge() {
System.out.println("수소차를 충전합니다.");
}
}
// Main
public class Main {
public static void main(String[] args) {
Car gasCar = new GasCar();
Car electricCar = new ElectricCar();
Car hydrogenCar = new HydrogenCar(); // 수소차 추가
gasCar.charge();
electricCar.charge();
hydrogenCar.charge(); // 수소차 추가
}
}
3. LSP (Liskov Substitution Principle) 리스코프 치환원칙
서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
인터페이스는 “공통 규약”이다.
인터페이스를 구현한 하위 인스턴스가 그 이상을 구현하면 “공통 규약”이라는 인터페이스의 의미가 퇴색된다.
따라서 인터페이스를 구현하는 하위 인스턴스는 인터페이스 대로 구현해야한다.
LSP를 지키지 않은 코드
<java />
abstract class Animal {
void speak() {}
}
class Cat extends Animal {
void speak() {
System.out.println("냐옹");
}
}
class Dog extends Animal {
void speak() {
System.out.println("멍멍");
}
}
class Fish extends Animal {
void speak() {
try {
throw new Exception("물고기는 말할 수 없음");
} catch (Exception e) {
e.printStackTrace();
}
}
}
LSP를 지킨 코드
<java />
abstract class Animal {
}
interface Speakable {
void speak();
}
class Cat extends Animal implements Speakable {
public void speak() {
System.out.println("냐옹");
}
}
class dog extends Animal implements Speakable {
public void speak() {
System.out.println("멍멍");
}
}
class Fish extends Animal {
}
4. ISP (Interface Segregation Principle) 인터페이스 분리 원칙
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리해야 한다.
즉, 자신이 사용하는 메소드만 존재하는 인터페이스를 상속해야 한다.
ISP를 지키지 않은 코드
<java />
public interface 전자기기 {
void powerOn();
void powerOff();
void playMusic();
void browseInternet();
}
ISP를 지킨 코드
<java />
public interface Powerable {
void powerOn();
void powerOff();
}
public interface MusicPlayable {
void playMusic();
}
public interface InternetBrowsable {
void browseInternet();
}
public class 스마트폰 implements Powerable, MusicPlayable, InternetBrowsable {
@Override
public void powerOn() {
System.out.println("켜지는중");
}
@Override
public void powerOff() {
System.out.println("꺼지는중");
}
@Override
public void playMusic() {
System.out.println("음악 재생 기능");
}
@Override
public void browseInternet() {
System.out.println("인터넷 검색 기능");
}
}
public class MP3 implements Powerable, MusicPlayable {
@Override
public void powerOn() {
System.out.println("켜지는중");
}
@Override
public void powerOff() {
System.out.println("꺼지는중");
}
@Override
public void playMusic() {
System.out.println("음악 재생 기능");
}
}
5. DIP (Dependency Inversion Principle) 의존관계 역전 원칙
추상화에 의존해야지, 구체화에 의존하면 안된다.
고수준 모듈은 저수준 모듈에 의존해서는 안된다.
DIP를 지키지 않은 코드
<java />
public class WindowsXPComputer {
private final StandardKeyboard keyboard;
private final BallMouse mouse;
public Windows98Machine() {
mouse = new BallMouse();
keyboard = new StandardKeyboard();
}
}
DIP를 지킨 코드
<java />
public class WindowsXPComputer {
private Keyboard keyboard;
private Mouse mouse;
public void WindowsXPComputer(Keyboard keyboard, Mouse mouse) {
this.keyboard = keyboard;
this.mouse = mouse;
}
public void typing(){
}
}
public interface Mouse {
void move();
}
public class VerticalMouse implements Mouse {
public void move() {}
}
public class BallMouse implements Mouse {
public void move() {}
}
public interface Keyboard {
// 키보드 관련 메서드 정의
}
public class StandardKeyboard implements Keyboard {
// StandardKeyboard의 구현
}
public class MechanicalKeyboard implements Keyboard {
// MechanicalKeyboard의 구현
}