Skip to main content

5 posts tagged with "Software"

View All Tags

· 3 min read

DIP Dependency Inversion Principle

  • DIP(Dependency Inversion Principle) - 의존관계 역전 원칙
    • 상위 모듈이 하위 모듈에 의존하면 안되고 두 모듈 모두 추상화에 의존하게 만들어야 한다는 원칙
    • 추상화를 진행해서 각각의 모듈에 더 추상화된 것에 의존하게 만들어야 한다는 것
    • 이렇게 코드를 설계해야 재사용에도 유용하고 하나를 수정했을 때 더욱 수정상황이 많이 없는 훌륭한 프로그램을 설계할 수 있다
    • Refactoring에서도 MVP 패턴을 사용하는 이유를 생각하면 된다

BadCase

class AccountManager {
func getAccount() -> String {
return "XX은행 00-0000-0000-00"
}
}

class BankService {
private let accountManager: AccountManager = AccountManager()

func transfer() {
let accountNumber = accountManager.getAccount()
print("1000원을 \(accountNumber)로 송금합니다.")
}
}

let bankService = BankService()
bankService.transfer()
  • AccountManager 객체가 BankService 내부에서 생성되고 사용되고 있어서 테스트에 용이하지 않다.
  • AccountManager의 변화가 생기면 BankService에서도 수정이 필요하게 된다.

NiceCase

protocol AccountProtocol {
func getAccount() -> String
}

class AccountManagerNice: AccountProtocol {
func getAccount() -> String {
return "XX은행 00-0000-0000-00"
}
}

class BankServiceNice {
let accountManager: AccountProtocol

init(accountManager: AccountProtocol) {
self.accountManager = accountManager
}

func transfer() {
print("1000원을 \(accountManager.getAccount())로 송금합니다.")
}
}

let accountManagerNice = AccountManagerNice()
let bankServiceNice = BankServiceNice(accountManager: accountManagerNice)
bankServiceNice.transfer()
  • protocol을 활용하여 AccountManager를 독립적으로 구현한다
  • BankService 생성자에 AccountProtocol을 사용하여 AccountManager를 주입시켜 처리한다
  • 이렇게 구한하게 되면 테스트 케이스 작성 시 Mock으로 간편하게 AccountManager를 만들어 테스트를 용이하게 처리할 수 있다.

참조

· 3 min read

Interface Segregation Principle

  • ISP(Interface Segregation Principle) - 인터페이스 분리 원칙
    • 인터페이스를 일반화하여 구현하지 않는 인터페이스를 채택하는 것보다 구체적인 인터페이스를 채택하는 것이 더 좋다는 원칙
    • 인터페이스를 설계할 때, 굳이 사용하지 않는 인터페이스는 채택하여 구현하지 말고 오히려 한 가지의 기능만을 가지더라도 정말 사용하는 기능만을 가지는 인터페이스로 분리하라는 원칙

BadCase

protocol Vehicle {
var wheels: Int { get set }

func moveWithWheel()

func stir()
}

class Tesla: Vehicle {
var wheels: Int = 4

func moveWithWheel() {
print("Move forward with \(wheels) wheels")
}

func stir() {
print("Can't stir")
}
}

class K1: Vehicle {
var wheels: Int = 0

func moveWithWheel() {
print("Move forward with \(wheels) wheels")
}

func stir() {
print("Can stir")
}
}
  • 포괄적인 인터페이스를 만드는 것은 좋지 않다. 기능별로 분리하여 한 가지 역할만 할 수 있도록 처리하는 것이 바람직하다.
  • 여러가지 일을하는 protocol은 분리하여 하나의 기능만 가능하도록 처리

NiceCase

protocol Boat {
func stir()
}

protocol Car {
var wheels: Int { get set }

func move()
}

class Benz: Car {
var wheels: Int = 4

func move() {
print("Move forward with \(wheels) wheels")
}
}

class DuckBoat: Boat {
func stir() {
print("Stiring with hands")
}
}
  • move, stir 등 여러 작업을 하고 있던 포괄적인 Vehicle protocol을 Boat, Car로 분리하여 각각의 역할만 수행하도록 변경
  • 해당 protocol을 Implement 하는 class는 해당하는 역할만 구현하면 된다

참조

· 3 min read

LSP(Liskov Substitution Principle) - 리스코프 치환 원칙

  • 부모(super class)로 동작하는 곳에서 자식(sub class)를 넣어주어도 대체가 가능해야 한다는 원칙
  • 자식 클래스를 구현할 때, 기본적으로 부모 클래스의 기능이나 능력들을 물려받는데 여기서 자식 클래스가 동작할 때, 부모 클래스의 기능들을 제한하면 안된다는 뜻
  • 상속을 할 때 완전한 상속이 아닌 경우는 Interface로 구현하는 것이 맞다(Refactoring 책에서도 나옴)
    • 자바의 stack이 부모의 기능을 모두 사용하지도 않으면서 array 상속받아 사용
  • 부모 클래스의 타입에 자식 클래스의 인스턴스를 넣어도 똑같이 동작하여야 한다

BadCase

class RectangleBad {
var width: Float = 0
var height: Float = 0

var area: Float {
return width * height
}
}

class SquareBad: RectangleBad {
override var width: Float {
didSet {
height = width
}
}
}

func printArea(of rectangle: RectangleBad) {
rectangle.height = 3
rectangle.width = 6
print(rectangle.area)
}

let rectangle = RectangleBad()
printArea(of: rectangle)

let square = SquareBad()
printArea(of: square)
  • Rectangle(부모) 클래스를 상속받은 Square(자식) 클래스에서 부모의 모든 자원을 활용하지 못하는 형태라 LSP에 어긋나게 된다.
  • 부모의 역할을 자신이 온전하게 대체하지 못함

NiceCase

protocol Shape  {
var area: Float { get }
}

class RectangleNice: Shape {
private let width: Float
private let height: Float

var area: Float {
return width * height
}

init(width: Float, height: Float) {
self.width = width
self.height = height
}
}

class SquareNice: Shape {
private let length: Float

var area: Float {
return self.length * self.length
}

init(length: Float) {
self.length = length
}
}

let rectangleNice = RectangleNice(width: 3, height: 6)
let squareNice = SquareNice(length: 6)
  • Protocol을 사용하여 area 함수를 구현하게 만들어 Rectangle, Square 각각이 area 함수를 구현한다
  • 구현부는 채택하는 하위 클래스로 넘기면 LSP의 원칙에 어긋나지 않는다

참조

· 2 min read

OCP(Open Closed Principle)

  1. OCP(Open Closed Principle) - 개방, 폐쇄 원칙
  • 확장에는 열려있으나 변경에는 닫혀있어야 한다는 원칙

  • 어떤 기능을 추가할 때, 기존의 코드는 만지지 않고 새로 동작하는 기능에 대해서만 코드가 작성 되어야한다

  • protocol을 활용하면 된다

  • 예제 코드

// OCP BadCase
class StudentBad {
func printName() {
print("Student")
}
}

class TeacherBad {
func printName() {
print("Teacher")
}
}

class ClassRoomBad {
private var students: [StudentBad] = [StudentBad(), StudentBad(), StudentBad()]
private var teachers: [TeacherBad] = [TeacherBad(), TeacherBad(), TeacherBad()]

func printAllName() {
students.forEach {
$0.printName()
}

teachers.forEach {
$0.printName()
}
}
}
  • 새로운 직업 이 추가될 경우 새로운 직업에 대한 class를 정의하고 ClassRoom의 코드에도 수정이 필요하기 때문에 OCP 원칙에 어긋나게 된다
// OCP NiceCase
protocol Person {
func printName()
}

class StudentNice: Person {
func printName() {
print("Student")
}
}

class TeacherNice: Person {
func printName() {
print("Teacher")
}
}

class ClassRoomNice {
private var persons: [Person] = [ StudentNice(), TeacherNice(), StudentNice(), TeacherNice()]

func printAllNames() {
persons.forEach {
$0.printName()
}
}
}
  • protocol을 사용하여 공통 타입를 만들어 Person으로 접근하면 새로운 직업이 추가 되어도 해당 직업의 대한 class만 구현해주면 된다

    참조

  • https://dongminyoon.tistory.com/49

· 3 min read

SOLID란

  • 객체지향 설계에 더 좋은 아키텍쳐를 설계하기 위해 지켜야하는 원칙들의 5가지를 앞의 약어만 따서 정리한 단어
  • 디자인 패턴 중, VIPER, MVVM들도 모두 이런 원칙에 입각하여 만들어진 패턴이다
  1. SRP(Single Responsibility Principle) - 단일 책임 원칙
  • 클래스나 함수를 설계할 때, 각 단위들은 단 하나의 책임만을 가져야한다는 원칙

  • 클래스나 함수가 새롭게 변해야 한다면 하나의 역할을 가진 상태에서 새로운 것으로 변해야한다는 것(즉, 하나의 일만하고 변해도 하나의 일만 해야 한다)

  • 예제 코드

// SRP( Single Responsibility Priciple) - 단일 책임 원칙
// API, Database, Decoding (Bad Case)
class LoginServiceBad {
func login(id: String, pw: String) {
let userData = requestlogin()
let user = decodeUserInfom(data: userData)
saveUserOnDatabase(user: user)
}

private func requestlogin() -> Data {
return Data()
}

private func decodeUserInfom(data: Data) -> User {
return User(name: "testUser", age: 10)
}

private func saveUserOnDatabase(user: User) {
print("User data \(user.name) are saved!!!")
}

}

class User {
var name: String
var age: Int

init(name: String, age: Int) {
self.name = name
self.age = age
}

private func toString() {
print("My Name is: \(self.name), and \(self.age) years old")
}
}
  • LoginService 클래스가 API request, Data decoding, Database control 3가지의 기능을 모두 구현하고 있어 SRP 원칙에 어긋나게 된다
// use Protocols to

protocol APIHanlderProtocol {
func requestLogin() -> Data
}

protocol DecodingHandlerProtocol {
func decode<T>(from data: Data) -> T
}

protocol DatabaseHandlerProtocol {
func saveOnDatabase<T>(inform: T)
}

class LoginServiceNice {
let apiHandler: APIHanlderProtocol
let decodingHandler: DecodingHandlerProtocol
let databaseHandler: DatabaseHandlerProtocol

init(
apiHandler: APIHanlderProtocol,
decodingHandler: DecodingHandlerProtocol,
databaseHandler: DatabaseHandlerProtocol
) {
self.apiHandler = apiHandler
self.decodingHandler = decodingHandler
self.databaseHandler = databaseHandler
}

func login() {
let loginData = apiHandler.requestLogin()
let user: User = decodingHandler.decode(from: loginData)
databaseHandler.saveOnDatabase(inform: user)
}
}

class RequestManager: APIHanlderProtocol {
func requestLogin() -> Data {
return Data()
}
}

class DecodingManager: DecodingHandlerProtocol {
func decode<T>(from data: Data) -> T {
return User(name: "testUserName", age: 20) as! T
}
}

class DatabaseManager: DatabaseHandlerProtocol {
func saveOnDatabase<T>(inform: T) {
print("Database completed Save process")
}
}


let loginServiceNice = LoginServiceNice(
apiHandler: RequestManager(),
decodingHandler: DecodingManager(),
databaseHandler: DatabaseManager()
)

loginServiceNice.login()

  • LoginSerice 클래스는 정말 로그인 관련된 내용만 구현하게 하고 API, Decoding, Database 는 각각의 핸들러를 만들어 외부에서 주입 받아 SRP 원칙을 따르도록 변경하여 처리한다

참조