Dart 설계 원칙

김도현·2023년 12월 19일

6가지 좋은 코드 작성을 위한 원칙

  1. DRY(Don’t Repeat Yourself)
  • 중복 코드가 있다면 메소드로 분리
// 중복 제거된 문자열 사용
void printMessage(String message) {
  print(message);
}

void main() {
  printMessage('안녕하세요, 환영합니다!');
  printMessage('안녕히 가세요, 다음에 또 만나요!');
}
  1. PIE(Program Intently and Expressively)
  • 애매한 이름 사용 금지
  • 누가 봐도 알기 쉬운 이름 사용
  • 컨벤션 따르기
  • 매직 넘버에 이름 붙이기
// 의도가 명확한 변수와 함수명
var width = 5;
var height = 3;

int calculateRectangleArea(int length, int breadth) {
  return length * breadth;
}
  1. SRP(Single Responsibility Principle)
  • 단일 책임 원칙
  • 1개의 클래스는 1개의 일만 수행
  • 한 부분의 에러를 수정하기 위해서는 그 클래스만 수정하면 됨
  • 클래스 분리가 심해지면 오히려 관리가 어렵기도 함
// 단일 책임을 갖는 클래스
class UserService {
  void saveUser(User user) {
    // 사용자 저장 로직
  }
}
class EmailService {
  void sendEmail(User user) {
    // 이메일 전송 로직
  }
}
  1. OCP(Open Closed Principle)
  • 확장에 대해서는 열려있고 (확장은 자유롭고), 변경에 대해서는 닫혀있다 (의존 부분의 변경은 불필요), 수정 없이 확장 가능하도록 하자
  • Iterable, Comparator 등이 좋은 예
  • String 의 경우는 상속 금지이므로 OCP에 반하는 클래스의 대표적인 예
  • 인터페이스를 적극 활용하여 확장 가능하게 하자
확장 가능한 디자인 (다형성 활용)
abstract class Shape {
  double calculateArea();
}
class Rectangle implements Shape {
  double width;
  double height;

  @override
  double calculateArea() {
    return width * height;
  }
}

class Circle implements Shape {
  double radius;

  @override
  double calculateArea() {
    return 3.14 * radius * radius;
  }
}
  1. SDP(Stable Dependencies Principle)
  • 안전한 것에 의존
  • 암호 처리 같이 한번 완성되면 수정될 가능성이 없는 클래스에 의존할 만 함
  • 가장 좋은 것은 특정 클래스가 아니라 인터페이스에 의존하는 것
  • 클래스는 생성자가 변할 수 있으나 인터페이스는 거의 그대로
// 주문(Order) 모듈이 결제(Payment) 모듈에 의존하지 않음
class Order {
  // 주문 로직

  void processPayment(Payment payment) {
    payment.makePayment();
  }
}

class Payment {
  // 결제 로직

  void makePayment() {
    // 결제 처리 로직
  }
}
  1. ADP(Acyclic Dependencies Principle)
  • 의존성 비순환 원칙
  • 의존 관계에 사이클이 발생되지 않게 한다
// 주문(Order) 모듈과 결제(Payment) 모듈이 순환 의존성이 없음
class Order {
  // 주문 로직

  void processPayment(Payment payment) {
    payment.makePayment();
  }
}

class Payment {
  // 결제 로직

  void makePayment() {
    // 결제 처리 로직
  }
}

SOLID 원칙

  1. SRP(Single Responsiblity Principle, 단일 책임 원칙): 위 내용과 동일
  1. OCP(Open Closed Principle, 개방-폐쇄 원칙): 위 내용과 동일
  1. LSP(Liskov Substitution Principle, 리스코프 치환 원칙)
  • 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙
  • 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다
  • 자식클래스는 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행
class Bird {
  void fly() {
    print('새가 날고 있습니다.');
  }
}

class Penguin extends Bird {
  // 펭귄은 날지 못하므로 fly 메서드를 오버라이드하지 않음
}

void makeBirdFly(Bird bird) {
  bird.fly();
}

void main() {
  Bird bird = Bird();
  Penguin penguin = Penguin();

  makeBirdFly(bird); // "새가 날고 있습니다."
  makeBirdFly(penguin);  // 펭귄은 날지 못하므로 fly 메서드 호출은 일어나지 않음
}
  1. ISP(Interface Segregation Principle, 인터페이스 분리 원칙)
  • 한 클래스는 자신이 사용하지않는 인터페이스는 구현하지 말아야 한다
  • 하나의 일반적인 인터페이스보다 여러개의 구체적인 인터페이스가 낫다
// ISP를 준수한 추상 클래스
abstract interface class Animal {
  void eat();
}

abstract class Flyable {
  void fly();
}

class Bird implements Animal, Flyable {
  @override
  void eat() {
    print('새가 먹이를 먹습니다.');
  }

  @override
  void fly() {
    print('새가 날고 있습니다.');
  }
}

class Penguin implements Animal {
  @override
  void eat() {
    print('펭귄이 먹이를 먹습니다.');
  }
}
  1. DIP(Dependency Inversion Principle, 의존 역전 원칙)
  • 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존하라는 것
  • 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺으라는 것
abstract class Switchable {
  void turnOn();
  void turnOff();
}

class LightBulb implements Switchable {
  @override
  void turnOn() {
    print('전등이 켜졌습니다.');
  }

  @override
  void turnOff() {
    print('전등이 꺼졌습니다.');
  }
}

class Switch {
  Switchable device;

  Switch(this.device);

  void operate() {
    device.turnOn(); // 고수준 모듈(Switch)이 추상화(Switchable)에 의존
  }
}

0개의 댓글