Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- RxCocoa
- rxswift
- ribs
- clean architecture
- ios
- swift documentation
- 리펙터링
- Protocol
- 리팩토링
- swiftUI
- tableView
- 클린 코드
- UITextView
- SWIFT
- uiscrollview
- UICollectionView
- 애니메이션
- 리펙토링
- Refactoring
- Xcode
- map
- Human interface guide
- 스위프트
- Clean Code
- combine
- collectionview
- Observable
- MVVM
- HIG
- uitableview
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[Clean Architecture] 3. 코드로 알아보는 SOLID - LSP(Liskov Substitution Principle) 리스코프 치환 원칙 본문
Clean Architecture/Clean Architecture 코드
[Clean Architecture] 3. 코드로 알아보는 SOLID - LSP(Liskov Substitution Principle) 리스코프 치환 원칙
jake-kim 2021. 9. 18. 23:490. 코드로 알아보는 SOLID - 클래스 다이어그램 필수 표현
1. 코드로 알아보는 SOLID - SRP(Single Responsibility Principle) 단일 책임 원칙
2. 코드로 알아보는 SOLID - OCP(Open Close Principle) 개방 폐쇄 원칙
3. 코드로 알아보는 SOLID - LSP(Liskov Substitution Principle) 리스코프 치환 원칙
4. 코드로 알아보는 SOLID - ISP(Interface Segregation Principle) 인터페이스 분리 원칙
5. 코드로 알아보는 SOLID - DIP(Dependency Inversion Principle, testable) 의존성 역전 원칙
6. 코드로 알아보는 SOLID - Coordinator 패턴 화면전환
7. 코드로 알아보는 SOLID - Network, REST (URLSession, URLRequest, URLSessionDataTask)
8. 코드로 알아보는 SOLID - 캐싱 Disk Cache (UserDefeaults, CoreData)
LSP
- Liskov Substitution Principle
- 리스코프 치환 원칙
- A가 사용하고 있는 B하위 타입이 무엇으로 바뀌든지 A에 영향을 주지 않는 상태
- 상속을 사용할 땐 다른 구현체로 갈아끼워도 A에 아무 영향을 주지 않는 것
주의) LSP를 위반하는 예제
- Rectangle의 width, height는 각 독립으로 사용되어서, 자식으로 직사각형만 올 수 있지만, 정사각형(square)는 사용할 수 없는 상태
코드로 알아보는 LSP
- Billing 앱: 현재 지출한 금액을 기준으로 계산을 해주고 view에 표출하는 앱
- 비즈니스 요구사항
- License별로 금액을 다르게 계산
- 1) personal은 사용한 금액에 * 1.2를 하여 세금 포함된 가격으로 계산
- 2) Business는 사용한 금액에 * 0.7를 하여 혜택을 부여한 가격으로 계산
- 현재 1)번과 2)번의 요구사항은 정해지지 않았고 자꾸 바뀌는 상태 > 두 비즈니스 로직을 변경하기 쉽게 LSP를 사용
- 자주 변경될거 같은 비즈니스 로직을 사용하는 부분은 Interface로 하고, 쉽게 갈아끼울수 있는 구현체들로 구성
- 테스트) random값을 이용하여 Personal 로직이나 Business로직이 나오도록 분리
// DI
let randomInt = Int.random(in: (0...1))
let useCase: LicenseUseCase
if randomInt == 0 {
useCase = PersonalUseCase()
} else {
useCase = BusinessUseCase()
}
let viewModel = BillingViewModelImpl(licenseUseCase: useCase)
let viewController = BillingViewController(viewModel: viewModel)
navigationController?.pushViewController(viewController, animated: true)
- LicenseUseCase 정의
protocol LicenseUseCase {
func calcFee(_ currentFee: Int) -> Double
}
- PersonalUseCase, BusinessUseCase 정의
class PersonalUseCase: LicenseUseCase {
func calcFee(_ currentFee: Int) -> Double {
return Double(currentFee) * 1.2
}
}
class BusinessUseCase: LicenseUseCase {
func calcFee(_ currentFee: Int) -> Double {
return Double(currentFee) * 0.7
}
}
- BillingViewModel 정의
protocol BillingViewModelInput {
func didTapButton()
}
protocol BillingViewModelOutput {
var licenseInfo: BehaviorRelay<String> { get }
}
protocol BillingViewModel: BillingViewModelInput, BillingViewModelOutput {}
class BillingViewModelImpl: BillingViewModel {
let licenseUseCase: LicenseUseCase
init(licenseUseCase: LicenseUseCase) {
self.licenseUseCase = licenseUseCase
}
// Output
let licenseInfo: BehaviorRelay<String> = .init(value: "")
// Input
func didTapButton() {
// 현재 사용한 금액을 2000원이라 가정
let result = licenseUseCase.calcFee(2000)
licenseInfo.accept("계산된 최종 금액 = \(result)")
}
}
- BillingViewController 정의
class BillingViewController: UIViewController {
var viewModel: BillingViewModel!
let disposeBag = DisposeBag()
lazy var feeButton: UIButton = {
let button = UIButton()
button.setTitle("fee 계산하기", for: .normal)
button.setTitleColor(.gray, for: .normal)
button.backgroundColor = .systemGray.withAlphaComponent(0.5)
return button
}()
lazy var billingListContainerStackView: UIStackView = {
let view = UIStackView()
view.spacing = 16.0
return view
}()
lazy var resultLabel: UILabel = {
let label = UILabel()
label.textColor = .gray
return label
}()
init(viewModel: BillingViewModel) {
super.init(nibName: nil, bundle: nil)
self.viewModel = viewModel
}
required init?(coder: NSCoder) {
fatalError()
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
addSubviews()
makeConstraints()
bindInput()
bindOutput()
}
private func setupViews() {
view.backgroundColor = .white
}
private func addSubviews() {
view.addSubview(feeButton)
view.addSubview(resultLabel)
}
private func makeConstraints() {
feeButton.snp.makeConstraints { maker in
maker.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(120)
maker.centerX.equalToSuperview()
}
resultLabel.snp.makeConstraints { maker in
maker.top.equalTo(feeButton.snp.bottom).offset(30)
maker.centerX.equalTo(feeButton)
}
}
private func bindInput() {
feeButton.rx.tap.subscribe(onNext: { [weak self] in self?.didTapButton() }).disposed(by: disposeBag)
}
private func didTapButton() {
viewModel.didTapButton()
}
private func bindOutput() {
viewModel.licenseInfo.subscribe(onNext: { [weak self] in self?.resultLabel.text = $0 }).disposed(by: disposeBag)
}
}
* 전체 소스 코드 `LSP 부분`: https://github.com/JK0369/SOLID
'Clean Architecture > Clean Architecture 코드' 카테고리의 다른 글
Comments