Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Letswift19-clean-architecture

435 views

Published on

레츠스위프트 워크숍 - 클린 코드와 클린 아키텍처

Published in: Software
  • Be the first to comment

Letswift19-clean-architecture

  1. 1. 🙌
  2. 2. (?)
  3. 3. 1. 🤓
  4. 4. • • https://swiftkorea.github.io/meetup/2#session-time-5 2018 • • https://www.slideshare.net/godrm/letswift18-1
  5. 5. ? 191 1% 58% 41% ( ) ( ?)
  6. 6. Software maintenance is not 'keep it working like before'. It is 'keep it being useful in a changing world' - Jessica Kerr
  7. 7. • • •UI • • • • • • • , • • https://soojin.ro/review/ https://google.github.io/eng-practices/review/
  8. 8. rpc: remove size limit on RPC server message freelist. Servers like FizzBuzz have very large messages and would benefit from reuse. Make the freelist larger, and add a goroutine that frees the freelist entries slowly over time, so that idle servers eventually release all freelist entries. Construct a Task with a TimeKeeper to use its TimeStr and Now methods. Add a Now method to Task, so the borglet() getter method can be removed (which was only used by OOMCandidate to call borglet’s Now method). This replaces the methods on Borglet that delegate to a TimeKeeper. Allowing Tasks to supply Now is a step toward eliminating the dependency on Borglet. Eventually, collaborators that depend on getting Now from the Task should be changed to use a TimeKeeper directly, but this has been an accommodation to refactoring in small steps. Continuing the long-range goal of refactoring the Borglet Hierarchy.
  9. 9. , ,
  10. 10. ? ? ? ? ?
  11. 11. ? + +
  12. 12. Main - , , , Reusability - DRY, components, generics Reliability - Exception handling and cleanup Extensibility Security - , , , Performance - , Lazy Loading, , Scalability - Usability -
  13. 13. 1. ( ) - - TDD - ( ) 2. ( ) - , , - private public 3. ( ) - - , 4. ( ) - , - .
  14. 14. 1. , , , • • • • • • •
  15. 15. string, array ,
  16. 16. ( )
  17. 17. 2. , , • • • • • • • •
  18. 18. 3. • • • , , , , • • , , , • , , • , ,
  19. 19. 4. SOLID .
  20. 20. 1. SRP (Single-Responsibility Principle) ( , , ) . . 2. OCP (Open-Close Principle) , . . 3. LSP (Liskov Substitution Principle) ( ) . ( ) . 4. DIP (Dependency-Inversion Principle) . ( ) , . 5. ISP (Interface-Segregation Principle) ( ) .
  21. 21. Employee + calculatePay + reportHours + save CFO COO CTO
  22. 22. #1 PayCalculator EmployeeSaver HourReporter + calculatePay + reportHours + saveEmployee Employee Data
  23. 23. #2 PayCalculator EmployeeSaver HourReporter + calculatePay + reportHours + saveEmployee Employee Data Employee Facade + calculatePay + reportHours + save
  24. 24. OutputView MyPoint /
  25. 25. OutputView MyPoint <<protocol>> Drawable Protocol Oriented Programming
  26. 26. ShapeFactory Point type ShapeType <Protocol> Line Triangle Rect Polygon
  27. 27. User1 User2 User3 Cafe +foo +bar +hop
  28. 28. User1 User2 <<interface>> FooCafe +foo User3 Cafe +foo +bar +hop <<interface>> BarCafe +bar <<interface>> HopCafe +hop
  29. 29. GameManager Player Impl <<interface>> PlayerFactory +makePlayer Player Factory +makePlayer <<interface>> Player ( ) ( )
  30. 30. 5. .
  31. 31. (Immutable) (Mutable) (Transaction Memory)
  32. 32. 5. GRASP 9
  33. 33. GRASP 9 9가지 General Responsibility Assignment Software Patterns 집합 객체 책임을 할당하는 것은 OOD(객체 지향 설계) 핵심 설계 방법 중에 하나 개발자와 설계자라면 이런 패턴에 익숙해져야 함 1.정보 담당자 Information Expert 2.소유권한 Creator 3.컨트롤러 Controller 4.낮은 연결 Low Coupling 5.높은 응집도 High Cohesion 6.간접 참조 Indirection 7.다형성 Polymorphism 8.순수 조립 Pure Fabrication 9.변화 보호 Protected Variations https://medium.com/@codesquad_yoda/ -grasp- -d5e37a1bb5dc
  34. 34. 🧑💻
  35. 35. godrm / LadderGame OH-MY / LadderGame master master 1. fork 2. clone ( ) step1 3. checkout -b step1 // // 4. add / commit 5. push step16. Pull Request 7. master http://github.com/godrm/LadderGame
  36. 36. class ReservationAgency { func reserve(screening : Screening, customer: Customer, audienceCount: Int) -> Reservation { let movie = screening.getMovie() var discountable = false for condition in movie.getDiscountConditions() { if condition.getType() == .Period { discountable = screening.getWhenScreened().getDayOfWeek().equals(codition.getDayOfWeek()) && condition.getStartTime().compareTo(screening.getWhenScreend().toLocalTime()) <= 0 && condition.getEndTime().compareTo(screening.getWhenScreend().toLocalTime()) <= 0 && } else { discountable = condition.getSequence() == screening.getSequence() } if discountable { break } } var fee : Money if discountable { var discountAmount = Money.ZERO switch movie.getMovieType() { case AMOUNT_DISCOUNT: discountAmount = movie.getDiscountAmount() case PERCENT_DISCOUNT: discountAmount = movie.getFee().times(movie.getDiscountPercent()) case NONE_DISCOUNT: break } fee = movie.getFee().minus(discountAmount).times(audienceCount) } else { free = movie.getFee() } return Reservation(custom, screening, fee, audienceCount) } } ReservationAgency
  37. 37. ReservationAgency class ReservationAgency { func reserve(screening : Screening, customer: Customer, audienceCount: Int) -> Reservation { let discountable = mayDiscountable(screening) let fee = calculateFee(with: screening, discountable: discountable, audienceCount: audienceCount) return Reservation(screening, customer, audienceCount, fee) } }
  38. 38. 2. 👨🎨
  39. 39. Abstraction
  40. 40. ➔ main() input() -> process() -> output() validate() format()save() ➔ - - - - -
  41. 41. Client Employee calculatePay() monthlyBasePay() SalariedEmployee HourlyEmployee calculatePay() monthlyBasePay() calculatePay() monthlyBasePay() func calculatePay(taxRate: Double) -> Double { if (hourly) { return calculateHourlyPay(taxRate) } return calculateSalariedPay(taxRage) }
  42. 42. • : goto • : + ( ) • : /
  43. 43. property method encapsulation inheritance polymorphism class instance design pattern
  44. 44. Encapsulation +
  45. 45. Main HL1 HL2 HL3 ML1 ML2 ML3 ML4 ML5 ML6 LL1 LL1 LL3 LL4 LL5 LL6 LL7 LL8 LL9 LL10 LL11 LL12
  46. 46. HL1 ML1 + Foo() <<Protocol>> AM + Foo() 호출을 하는 모듈과 호출 받는 모듈 모두 소스 코드 의존성을 원하는 방향으로 설정할 수 있다. 소스 코드 의존성을 원하는 방향으로 관리할 수 있기 때문에 모듈, 컴포넌트 또는 배포가능한 단위로 서로 의존하지 않도록 컴파일할 수 있다. 결국 서로 독립적으로 배포를 할 수 있다. (배포 독립성)
  47. 47. Financial Report Controller Financial Report Presenter <I> Screen Presenter Screen View Model Screen View Web View <DS> <I> Financial Report Request Financial Report Requester Financial Report Response <DS> <DS> <I> Financial Report Generator Financial Report Gateway <I> Financial Report Entities Financial Data Mapper Financial Database Printer Presenter Print View Model Print View PDF View <DS> <I> Screen Presenter Printer Presenter Controller Interactor Database PDF ViewWeb View
  48. 48. Financial Report Controller Financial Report Interactor Financial Database PDF ViewWeb View Screen Presenter Print Presenter
  49. 49. http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
  50. 50. <I> <I>
  51. 51. View View Model Presenter Controller Input Data Input Boundary Output Boundary Output Data Data Access Interface Data Access Database Entities Use Case Interactor <I> <DS> <I> <DS> <I> <DS>
  52. 52. // / View Controller Model Presenter View
  53. 53. Model - View - Controller Controller : Coordination Model : Data View : Display / UIViewController NSObject UIView
  54. 54. 1. : , ? 2. : - ? 3. : ? 4. : , ? 5. : ? Point of View
  55. 55. MVC : objc App Architecture
  56. 56. MVVM + C : objc App Architecture
  57. 57. VIPER : objc App Architecture
  58. 58. 3. 🧹 POP = Value Type + OOP + FP
  59. 59. Classes Are Awesome • Encapsulation • Access Control • Abstraction • Namespace • Expressive Syntax • Extensibility ClassesClassesClassesTypes I can do all that with structs and enums. WWDC 2015 : POP
  60. 60. POP = Value Type + OOP + FP
  61. 61. class Ordered { func precedes(other: Ordered) -> Bool { fatalError("implement me!") } } class Number : Ordered { var value: Double = 0 override func precedes(other: Ordered) -> Bool { return self.value < (other as! Number).value } } OOP with Class
  62. 62. protocol Ordered { func precedes(other: Self) -> Bool } struct Number : Ordered { var value: Double = 0 func precedes(other: Number) -> Bool { return self.value < other.value } } POP with struct
  63. 63. • • . • GRASP
  64. 64. : 1* * 1 1 1..*
  65. 65. Information Expert 1: ? ?:Screening
  66. 66. Information Export 1: ?:Screening 2: :Movie 3*: :Discount Condition :Discount Condition :Discount Condition
  67. 67. Screening Movie Discount Condition 1: 2: 3:
  68. 68. (High Cohesion) (Low Cohesion)
  69. 69. (High Coupling) (Low Coupling) AA
  70. 70. Creator 1: ?:Screening 2: :Movie 3*: :Discount Condition :Discount Condition :Discount Condition :Reservation 4: <<create>>
  71. 71. class Screening { func reserve(with customer: Customer, audienceCount: Int) -> Reservation { } } class Screening { private var movie : Movie private var sequence : Int private var whenScreened : DateTime func reserve(with customer: Customer, audienceCount: Int) -> Reservation { } } class Screening { private var movie : Movie private var sequence : Int private var whenScreened : DateTime func reserve(with customer: Customer, audienceCount: Int) -> Reservation { return Reservation(with: customer, screening: self, fee:calculateFee(audienceCount), audienceCount) } func calculateFee(int audienceCount) -> Money { return movie.calculateMovieFee(self).times(audienceCount) } } Screening
  72. 72. class Movie { func calculateMovieFee(screening: Screening) -> Money { } } class Movie { private let title : String private let runningTime : TimeInterval private let fee : Money private var discountConditions = Array<DiscountCondition>() private let movieType : MovieType private let discountAmount : Money private let discountPercent : Double func calculateMovieFee(screening: Screening) -> Money { } } Movie
  73. 73. class Movie { enum MovieType { case AmountDiscount case PercentDiscount case NoneDiscount } private let title : String private let runningTime : TimeInterval private let fee : Money private var discountConditions = Array<DiscountCondition>() private let movieType : MovieType private let discountAmount : Money private let discountPercent : Double func calculateMovieFee(screening: Screening) -> Money { if isDiscountable(for: screening) { return fee.minus(calculateDiscountAmount()) } return fee } private func isDiscountable(for: Screening) -> Bool { return discountConditions.filter{ $0.isSatisfied(by: screening) }.count > 0 } } Movie
  74. 74. class DiscountCondition { func isSatisfied(by screening: Screening) -> Bool { } } class DiscountCondition { private let type : DiscountConditionType private let sequence : Int private let dayOfWeek : DayOfWeek private let startTime : Date private let endTime : Date func isSatisfied(by screening: Screening) -> Bool { if type == .period { return isSatisfiedByPeriod(screening) } return isSatisfiedBySequence(screening) } private func isSatisfiedByPeriod(_ screening: Screening) -> Bool { return dayOfWeek.equals(screening.whenScreened.dayOfWeek) && startTime.compare(to: screening.whenScreended.toLocalTime() <= 0) && endTime.isAfter(to: screening.whenScreended.toLocalTime() >= 0) } private func isSatisfiedBySequence(_ screening: Screening) -> Bool { return sequence == screening.sequence } } DiscountCondition
  75. 75. class Screening { private var movie : Movie private(set) var sequence : Int private(set) var whenScreened : DateTime func reserve(with customer: Customer, audienceCount: Int) -> Reservation { return Reservation(with: customer, screening: self, fee:calculateFee(audienceCount), audienceCount) } func calculateFee(int audienceCount) -> Money { return movie.calculateMovieFee(self).times(audienceCount) } } Screening & DiscountCondition class DiscountCondition { enum DiscountConditionType { case Sequence, Period } private let type : DiscountConditionType private let sequence : Int private let dayOfWeek : DayOfWeek private let startTime : DateTime private let endTime : DateTime //…
  76. 76. SRP ( )
  77. 77. class PeriodCondition { private let dayOfWeek : DayOfWeek private let startTime : Date private let endTime : Date init(dayOfWeek: DayOfWeek, startTime: DateTime, endTime: DateTime) { self.dayOfWeek = dayOfWeek self.startTime = startTime self.endTime = endTime } func isSatisfied(by screening: Screening) -> Bool { return dayOfWeek.equals(screening.whenScreened.dayOfWeek) && startTime.compare(to: screening.whenScreended.toLocalTime() <= 0) && endTime.isAfter(to: screening.whenScreended.toLocalTime() >= 0) } } PeriodCondition - SequenceCondition class SequenceCondition { private let sequence : Int init(with sequence: Int) { self.sequence = sequence } func isSatisfied(by screening: Screening) -> Bool { return sequence == screening.sequence } }
  78. 78. :Movie :Period Condition :Movie :Sequence Condition Moive
  79. 79. class Movie { enum MovieType { case AmountDiscount case PercentDiscount case NoneDiscount } private let title : String private let runningTime : TimeInterval private let fee : Money private let movieType : MovieType private let discountAmount : Money private let discountPercent : Double private var periodConditions = Array<PeriodCondition>() private var sequenceConditions = Array<SequenceCondition>() func calculateMovieFee(screening: Screening) -> Money { if isDiscountable(for: screening) { return fee.minus(calculateDiscountAmount()) } return fee } private func mayPeriodConditions(with screening: Screening) -> Bool { return periodConditions.filter{ $0.isSatisfied(by: screening) }.count > 0 } private func maySequenceConditions(with screening: Screening) -> Bool { return sequenceConditions.filter{ $0.isSatisfied(by: screening) }.count > 0 } private func isDiscountable(for: Screening) -> Bool { return mayPeriodConditions(with: screening) || maySequenceConditions(with: screening) } Movie
  80. 80. :Sequence Condition :Period Condition :Movie :Discount Condition DiscountCondition protocol DiscountCondition { func isSatisfied(by screening: Screening) -> Bool } class PeriodCondition : DiscountCondition { } class SequenceCondition : DiscountCondition { }
  81. 81. class Movie { enum MovieType { case AmountDiscount case PercentDiscount case NoneDiscount } private let title : String private let runningTime : TimeInterval private let fee : Money private let movieType : MovieType private let discountAmount : Money private let discountPercent : Double private var discountConditions = Array<DiscountCondition>() func calculateMovieFee(screening: Screening) -> Money { if isDiscountable(for: screening) { return fee.minus(calculateDiscountAmount()) } return fee } private func isDiscountable(for: Screening) -> Bool { return discountConditions.filter{ $0.isSatisfied(by: screening) }.count > 0 } Movie
  82. 82. Protected Variations Movie -title +calculateMovieFee() Screening <<interface>> DiscountCondition + isSatisfiedBy() SequenceCondition + isSatisfiedBy() PeriodCondition + isSatisfiedBy() + reserve() movie discountConditions
  83. 83. Movie Movie -title +calculateMovieFee() Screening <<interface>> DiscountCondition + isSatisfiedBy() SequenceCondition + isSatisfiedBy() PeriodCondition + isSatisfiedBy() + reserve() movie discountConditions Percent DiscountMovie None DiscountMovie Amount DiscountMovie #calculateDiscountAmount() #calculateDiscountAmount() #calculateDiscountAmount()
  84. 84. anObject foobar[ ]; instance method self anObject action message[ ] anObject.foobar()
  85. 85. , , self anObject 1. message[ ] operation method2. 3.
  86. 86.
  87. 87. before class ReservationAgency { func reserve(screening : Screening, customer: Customer, audienceCount: Int) -> Reservation { let movie = screening.getMovie() var discountable = false for condition in movie.getDiscountConditions() { if condition.getType() == .Period { discountable = screening.whenScreened().getDayOfWeek().equals(codition.getDayOfWeek()) && condition.getStartTime().compareTo(screening.whenScreend().toLocalTime()) <= 0 && condition.getEndTime().compareTo(screening.whenScreend().toLocalTime()) <= 0 && } else { discountable = condition.getSequence() == screening.getSequence() } if discountable { break } } var fee : Money if discountable { var discountAmount = Money.ZERO switch movie.getMovieType() { case AMOUNT_DISCOUNT: discountAmount = movie.getDiscountAmount() case PERCENT_DISCOUNT: discountAmount = movie.getFee().times(movie.getDiscountPercent()) case NONE_DISCOUNT: break } fee = movie.getFee().minus(discountAmount).times(audienceCount)
  88. 88. after class ReservationAgency { func reserve(screening : Screening, customer: Customer, audienceCount: Int) -> Reservation { let fee = screening.calculateFee(audienceCount) return Reservation(custom, screening, fee, audienceCount) } }
  89. 89. exception class PeriodCondition : DiscountCondition { private let dayOfWeek : DayOfWeek private let startTime : Date private let endTime : Date init(dayOfWeek: DayOfWeek, startTime: DateTime, endTime: DateTime) { self.dayOfWeek = dayOfWeek self.startTime = startTime self.endTime = endTime } func isSatisfied(by screening: Screening) -> Bool { return dayOfWeek.equals(screening.whenScreened.dayOfWeek) && startTime.compare(to: screening.whenScreended.toLocalTime() <= 0) && endTime.isAfter(to: screening.whenScreended.toLocalTime() >= 0) } } class PeriodCondition : DiscountCondition { private let dayOfWeek : DayOfWeek private let startTime : Date private let endTime : Date // func isSatisfied(by screening: Screening) -> Bool { return screening.isDiscountable(dayOfWeek: dayOfWeek, startTime: startTime, endTime: endTime) } } extension Screening { func isDiscountable(dayOfWeek: DayOfWeek, startTime: DateTime, endTime: DateTime) -> Bool { return dayOfWeek.equals(whenScreened.dayOfWeek) && startTime.compare(to: whenScreended.toLocalTime() <= 0) && endTime.isAfter(to: whenScreended.toLocalTime() >= 0) } }
  90. 90. –JK “iOS , .”

×