Flutter: SOLID 원칙이란 무엇인가

yeahsilver·2023년 6월 16일
1
post-thumbnail

SOLID 원칙이란?

SOLID 원칙은 아래 단어들의 약어이다.

  • Single Responsibility Principle (단일 책임의 원칙)
  • Open-Closed Principle (개방 폐쇠의 원칙)
  • Liskov Substitution Principle (리스코브 치환의 원칙)
  • Interface Segregation Principle (인터페이스 분리의 원칙)
  • Dependency Inversion Principle (의존성 역전의 원칙)

각각이 어떤 의미를 띄는지 알아보자.

SRP (Single Responsibility Principle: 단일 책임의 원칙)

정의

  • 하나의 클래스는 하나의 기능만을 가지며 클래스가 가지는 모든 구현채는 하나의 책임을 수행하는데 집중되어야 하는 원칙
  • 책임 영역이 확실하게 분리되어있기 때문에 코드의 가독성 향상, 유지 보수가 용이

적용 방법

적용 전

  • 하나의 클래스가 여러가지의 역할을 담당하는 형태로 코드 작성
  • Result 클래스는 checkResult, isValidate, getRequest, show라는 각기 다른 기능을 모두 가지고 있음
  • 아래의 코드는 명백히 SRP에 위반된 코드
class Result {
	String checkResult() {...}
	void isValidate() {...}
    String getRequest() {...}
    void show() {...}
}

적용 후

  • 해당 코드에 SRP 원칙을 적용한다면 아래와 같이 처리 가능
class Result {
	String checkResult() {...};
    ...
}

class Validate {
	void isValidate() {...};
    ...
}

class Network {
	String getRequest() {...}
    ...
}

class Printing {
	void show(){...}
    ...
}

OCP (Open-Closed Principle: 개방폐쇄의 원칙)

정의

  • 소프트웨어의 구성요소 (컴포넌트, 클래스, 모듈, 함수)는 확장에 열려있고, 변경에는 닫혀있어야 한다는 원칙
  • 변경을 위한 비용을 줄이고 확장을 위한 비용은 극대화
  • 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 함

적용 방법

  • 변경(확장)될 것과 변하지 않을 것을 엄격하게 구분
  • 모듈이 만나는 지점에 인터페이스 정의
  • 정의한 인터페이스에 의존하도록 코드 작성

적용 전

  • 매개변수로 들어온 교통수단의 타입을 체크하여 타입에 맞는 메소드를 실행하는 형태
  • 다른 교통수단이 추가될 경우 조건절에 추가된 교통수단에 대한 조건을 추가해야함
  • 즉, 확장성을 전혀 고려하지 않은 코드
class VehicleMileage {
	void showMileage(dynamic anyVehicle) {
    	if(anyVehicle is Bike) {
        	print(anyVehicle.getBikeMileage());
        } else if(anyVehicle is Car) {
        	print(anyVehicle.getCarMileage());
        }
    }
}

class Bike {
	String getBikeMileage() = "45km/l";
}

class Car {
	String getCarMileage() = "14km/l";
}

적용 후

  • Vehicle이라는 상위 인터페이스 정의
  • 교통수단이 추가되더라도 조건이 추가되지 않아도 됨 (= 변경하지 않아도 되기에 기능 확장에 용이)

class VehicleMileage {
	void showMileage(Vehicle vehicle) {
    	print(vehicle.getMileage());
    }
}

class Bike extends Vehicle {
	String getMileage() = "45km/l";
}

class Car extends Vehicle{
	String getMileage() = "14km/l";
}

abstract class Vehicle {
	String getMilage();
}

LSP (Liskov Substitution Principle: 리스코프 치환의 법칙)

정의

  • 하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행할 수 있어야 한다는 원칙
  • 상속 관계에서는 일반화 관계가 성립해야함
  • 재사용 목적으로 사용하는 경우, 상속 관계가 아닌 클래스들을 상속 관계로 설정하면, 해당 원칙을 위배하는 것임
  • 즉, 리스코프 치환의 법칙을 위반하는 것은, 개방 폐쇄 원칙 또한 위반하는 행위

적용 방법

  • 두 개체가 동일한 작업을 수행한다면, 두 개체를 하나의 클래스로 표현하고, 이들을 구분할 수 있는 필드 생성
  • 똑같은 연산을 제공해야한다면 공통의 인터페이스를 생성하고 구현
  • 공통된 연산이 존재하지 않는다면 별개인 2개의 클래서 생성

적용 전

  • 클래스에서 별개의 기능을 가진 두개의 역할을 수행 (checkResult(), codingTestResult())
  • 똑같은 연산을 수행하는 개체가 두가지 존재
abstract class Result {
  checkResult();
  codingTestResult();
}

class MechanicalBranch extends Result {
  
  checkResult() {...}

  
  codingTestResult() {...}
}

class ComputerScienceBranch extends Result {
  
  checkResult() {...}

  
  codingTestResult() {...}
}

적용 후

  • 똑같은 연산을 수행하는 개체를 추상 클래스로 분리
  • implements 명령어를 통해 구현할 부분만 추출해서 사용
  • 각 클래스에서는 하나의 기능만 담당하도록 처리
abstract class OfflineResult {
  checkResult();
}

abstract class CodingResult {
  codingTestResult();
}

class MechanicalBranch implements OfflineResult {
  
  checkResult() {...}
}

class ComputerScienceBranch implements OfflineResult, CodingResult {
  
  checkResult() {...}

  
  codingTestResult() {...}
}

ISP (Interface Segregation Principle: 인터페이스 분리의 원칙)

정의

  • 클라이언트는 자신이 사용하는 메소드에만 의존
  • 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 않아야함
  • 즉, 클라이언트가 필요로하는 인터페이스들을 분리함으로써 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 구현

적용 방법

  • 클래스의 상속을 이용하여 인터페이스 분리
  • delegate를 이용하여 인터페이스 분리

적용 전

  • MechanicalBranchcodingTestResult()는 아무런 클래스에서도 사용되지 않은 메소드
  • Result라는 추상 클래스에서 두개의 다른 역할을 수행
abstract class Result {
  checkResult();
  codingTestResult();
}

class MechanicalBranch implements Result {
  
  checkResult() {...}
	
  // 사용하지 않는 메소드라고 가정
  
  codingTestResult() {...}
}

class ComputerScienceBranch implements Result {
  
  checkResult() {...}

  
  codingTestResult() {...}
}

적용 후

  • 사용되지 않는 메소드 삭제
  • 다른 기능들을 다른 클래스로 분리
abstract class OfflineResult {
  checkResult();
}

abstract class CodingResult {
  codingTestResult();
}

class MechanicalBranch implements OfflineResult {
  
  checkResult() {...}
}

class ComputerScienceBranch implements OfflineResult, CodingResult {
  
  checkResult() {...}

  
  codingTestResult() {...}
}

DIP (Dependency Inversion Principle: 의존성 역전의 원칙)

정의

  • 구체화된 클래스에 의존하기보다는 추상 클래스나 인터페이스에 의존해야한다는 원칙
  • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안됨
  • 즉, 저수준 모듈이 변경되어도 고수준 모듈은 변경이 필요없는 형태가 이상적

적용 방법

적용

  • CheckOut클래스는 Payment 클래스가 어떤 방식으로 작동되는지 알지 못하는 상태
  • 그렇지만, pay.payment()라는 메소드를 사용하여 Payment 클래스에 접근 가능
abstract class Payment {
 payment();
}

class PaymentViaCreditCard implements Payment {
  
  payment() {...}
}

class PaymentViaDebitCard implements Payment {
  
  payment() {...}  
}
class PaymentViaBhimUPI implements Payment {
  
  payment() {...} 
}

class Checkout {
  checkOut(Payment pay){
    pay.payment();
  }
}

Reference

profile
Hello yeahsilver's world!

0개의 댓글