8 attempts to introduce you to iOS lean programming

8 attempts to introduce you to iOS lean programming

[[153315]]

Opening

Today, we will start with a small function and implement it without thinking.

Product Repository: Filtering Operation

Code start

There is a product library, we want to filter it.

The first requirement is not complicated.

Requirement 1: Find all products in the warehouse that are red

First Attempt: Hard Code

Let's first implement it in the simplest way, hard coding

  1. - (NSArray *)findAllRedProducts:(NSArray *)products
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if (product.color == RED) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

If the world were eternally static, this would be understandable, but the world is often not like this.

Then, the second requirement came

Requirement 2: Find all green products in the warehouse

Second Attempt: Parameterizing

Copy-Paste is the most common mistake made by most programmers, which introduces a lot of duplicate code.

  1. - (NSArray *)findAllGreenProducts:(NSArray *)products
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if (product.color == GREEN) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

In order to eliminate hard coding and obtain reusable code, simple parameterized design can be introduced.

  1. - (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if (product.color == color) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

Finally, I can rest assured. How can our product manager make you feel comfortable at this time? Requirement 3 is here again.

Requirement 3: Find all products whose weight is less than 10

Third Attempt: Parameterizing with Every Attribute You Can Think Of

Most programmers still use Copy-Paste to solve this problem. To avoid the bad habit of Copy-Paste, the most effective feedback is to disable this shortcut key, so that you can remind yourself to make better designs every time you try Copy-Paste.

  1. - (NSArray *)findProducts:(NSArray *)products byWeith:( float )weight
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if (product.weight < weight) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

In order to eliminate duplicate code between the two, simple parameterization often cannot completely solve such problems. On the contrary, it will introduce excessive complexity and incidental costs.

  1. - (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color byWeith:( float )weight type:( int )type
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if ((type == 1 ) && product.color == color) {
  6. [list addObject:product];
  7. continue ;
  8. }
  9. else   if ((type == 2 ) && (product.weight < weight))
  10. {
  11. [list addObject:product];
  12. continue ;
  13. }
  14. }
  15. return list;
  16. }

In daily work, this implementation method is very common. The parameter list of the function continues to increase as the demand increases, the function logic assumes more and more responsibilities, and the logic becomes more and more difficult to control.

Designs that respond to changes through parameter configuration are often failed designs

It is easy to lead to complex logic control, causing additional accidental complexity

Forth Attempt: Abstracting over Criteria

To do this, abstraction is needed so that the traversal algorithm and search criteria can change independently without affecting each other.

  1. @interface ProductSpec : NSObject
  2. - (BOOL)satisfy:(Product *)product;
  3. @end  

At this point, the filter's algorithm logic is closed. Of course, the function name needs to be renamed to make its algorithm implementation more universal.

  1. - (NSArray *)findProducts:(NSArray *)products bySpec:(ProductSpec *)spec
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if ([spec satisfy:product]) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

Encapsulate various changes through reusable classes to keep the factors of change within the smallest range.

  1. @interface ColorSpec()
  2. @property (nonatomic, assign) ProductColor color;
  3. @end  
  4. @implementation ColorSpec
  5. + (instancetype)specWithColor:(ProductColor)color
  6. {
  7. ColorSpec *spec = [[ColorSpec alloc] init];
  8. spec.color = color;
  9. return spec;
  10. }
  11. - (BOOL)satisfy:(Product *)product
  12. {
  13. return product.color == RED;
  14. }
  15. @end  
  16. @interface BelowWeightSpec()
  17. @property (nonatomic, assign) float limit;
  18. @end  
  19. @implementation BelowWeightSpec
  20. + (instancetype)specWithBelowWeight:( float )limit
  21. {
  22. BelowWeightSpec *spec = [[BelowWeightSpec alloc] init];
  23. spec.limit = limit;
  24. return spec;
  25. }
  26. - (BOOL)satisfy:(Product *)product
  27. {
  28. return (product.weight < _limit);
  29. }
  30. @end  

The user interface has also become much simpler and more expressive.

  1. [self findProducts:_products bySpec:[ColorSpec specWithColor:RED]];

This is a classic OO design. Readers who are familiar with design patterns are already accustomed to it. Design patterns are good things, but they are often abused. For this reason, we cannot copy them rigidly, but introduce design patterns to get a simpler design. This process is very natural.

I talked to the masters and asked them why the design pattern was introduced here. The answer I got was: intuition. Forget all the design patterns. It doesn’t matter if it is a pattern or not. If the design is simple, it is a pattern.

There is also an obvious bad smell. Both ColorSpec and BelowWeightSpec need to inherit ProductSpec, both need to define a constructor and a private field, and override the satisfy method, which are full of repeated structures.

Do you think the current writing method is sufficient? Don't worry, let's take a look at the next requirement

Requirement 4: Find all products whose color is red and whose weight is less than 10

  1. Firth Attempt: Composite Criteria
  2.  
  3. According to the existing code structure, it is often easy to design an implementation similar to ColorAndBelowWeightSpec.
  4. @interface ColorAndBelowWeigthSpec()
  5. @property (nonatomic, assign) ProductColor color;
  6. @property (nonatomic, assign) float limit;
  7. @end  
  8. @implementation ColorAndBelowWeigthSpec
  9. + (instancetype)specWithColor:(ProductColor)color beloWeigth:( float )limit
  10. {
  11. ColorAndBelowWeigthSpec *spec = [[ColorAndBelowWeigthSpec alloc] init];
  12. spec.color = color;
  13. spec.limit = limit;
  14. return spec;
  15. }
  16. - (BOOL)satisfy:(Product *)product
  17. {
  18. return product.color == _color || (product.weight < _limit);
  19. }
  20. @end  

There are two obvious bad smells:

Naming containing "and" is often a signal that violates the single responsibility principle.

There is an obvious duplication between the implementation of ColorAndBelowWeightSpec and ColorSpec,BelowWeightSpec

At this point, we need to find a more essential abstraction to express the design, and/or/not semantics can best solve this type of problem.

Composite Spec: AndSpec, OrSpec, NotSpec

Atomic Spec: ColorSpec, BeblowWeightSpec

  1. @interface AndSpec()
  2. @property (nonatomic, strong) NSArray *specs;
  3. @end  
  4. @implementation AndSpec
  5. + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION
  6. {
  7. va_list args;
  8. va_start( args, spec );
  9. NSMutableArray *mArray = [@[spec] mutableCopy];
  10. for ( ;; )
  11. {
  12. id tempSpec = va_arg( args, id );
  13. if (tempSpec == nil)
  14. break ;
  15. [mArray addObject:tempSpec];
  16. }
  17. va_end( args );
  18. AndSpec *andSpec = [[AndSpec alloc] init];
  19. andSpec.specs = [mArray copy];
  20. return andSpec;
  21. }
  22. - (BOOL)satisfy:(Product *)product
  23. {
  24. for (ProductSpec *spec in _specs) {
  25. if (![spec satisfy:product]) {
  26. return NO;
  27. }
  28. }
  29. return YES;
  30. }
  31. @end  
  32. @interface OrSpec ()
  33. @property (nonatomic, strong) NSArray *specs;
  34. @end  
  35. @implementation OrSpec
  36. + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION
  37. {
  38. va_list args;
  39. va_start( args, spec );
  40. NSMutableArray *mArray = [@[spec] mutableCopy];
  41. for ( ;; )
  42. {
  43. id tempSpec = va_arg( args, id );
  44. if (tempSpec == nil)
  45. break ;
  46. [mArray addObject:tempSpec];
  47. }
  48. va_end( args );
  49. OrSpec *orSpec = [[OrSpec alloc] init];
  50. orSpec.specs = [mArray copy];
  51. return orSpec;
  52. }
  53. - (BOOL)satisfy:(Product *)product
  54. {
  55. for (ProductSpec *spec in _specs) {
  56. if ([spec satisfy:product]) {
  57. return YES;
  58. }
  59. }
  60. return NO;
  61. }
  62. @end  
  63. @interface NotSpec ()
  64. @property (nonatomic, strong) ProductSpec *spec;
  65. @end  
  66. @implementation NotSpec
  67. + (instancetype)spec:(ProductSpec *)spec
  68. {
  69. NotSpec *notSpec = [[NotSpec alloc] init];
  70. notSpec.spec = spec;
  71. return notSpec;
  72. }
  73. - (BOOL)satisfy:(Product *)product
  74. {
  75. if (![_spec satisfy:product]) {
  76. return YES;
  77. }
  78. return NO;
  79. }
  80. @end  

The requirement can be met by combining ColorSpec and BelowWeightSpec through AndSpec, which is simple, beautiful and expressive.

  1. [self findProducts:_products bySpec:[AndSpec spec:[ColorSpec specWithColor:RED], [BelowWeightSpec specWithBelowWeight: 10 ], nil]];

But there are two serious drawbacks to this design:

There is obvious code duplication between AndSpec and OrSpec. The first intuition of OO design is to eliminate duplication by extracting base classes.

  1. @interface CombinableSpec ()
  2. @property (nonatomic, strong) NSArray *specs;
  3. @end  
  4. @implementation CombinableSpec
  5. + (instancetype)spec:(CombinableSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION
  6. {
  7. va_list args;
  8. va_start( args, spec );
  9. NSMutableArray *mArray = [@[spec] mutableCopy];
  10. for ( ;; )
  11. {
  12. id tempSpec = va_arg( args, id );
  13. if (tempSpec == nil)
  14. break ;
  15. [mArray addObject:tempSpec];
  16. }
  17. va_end( args );
  18. CombinableSpec *combinableSpec = [[CombinableSpec alloc] init];
  19. combinableSpec.specs = [mArray copy];
  20. return combinableSpec;
  21. }
  22. - (BOOL)satisfy:(Product *)product
  23. {
  24. for (ProductSpec *spec in _specs) {
  25. if ([spec satisfy:product] == ​​_shortcut) {
  26. return _shortcut;
  27. }
  28. }
  29. return !_shortcut;
  30. }
  31. @end  
  32. @implementation AndSpec
  33. - (instancetype)init
  34. {
  35. self = [ super init];
  36. if (self) {
  37. self.shortcut = NO;
  38. }
  39. return self;
  40. }
  41. @end  
  42. @implementation OrSpec
  43. - (instancetype)init
  44. {
  45. self = [ super init];
  46. if (self) {
  47. self.shortcut = YES;
  48. }
  49. return self;
  50. }
  51. @end  

A large number of initialization methods are dazzling

  1. [self findProducts:_products bySpec:[NotSpec spec:[AndSpec spec:[ColorSpec specWithColor:RED], [BelowWeightSpec specWithBelowWeight: 10 ], nil]]];
  2.  
  3. Sixth Attempt: Using DSL

DSL can be introduced to improve the readability of the program and make the code more expressive.

Let's start by adding some DSL:

  1. static ProductSpec *COLOR(ProductColor color)
  2. {
  3. return [ColorSpec specWithColor:RED];
  4. }
  5. static ProductSpec *BELOWWEIGHT( float limit)
  6. {
  7. return [BelowWeightSpec specWithBelowWeight:limit];
  8. }
  9. static ProductSpec *AND(ProductSpec *spec1, ProductSpec *spec2)
  10. {
  11. return [AndSpec spec:spec1, spec2, nil];
  12. }
  13. static ProductSpec *OR(ProductSpec *spec1, ProductSpec *spec2)
  14. {
  15. return [OrSpec spec:spec1, spec2, nil];
  16. }
  17. static ProductSpec *NOT(ProductSpec *spec)
  18. {
  19. return [NotSpec spec:spec];
  20. }

This is how our code looks like

  1. [self findProducts:_products bySpec:NOT(AND(COLOR(RED), BELOWWEIGHT( 10 )))];
  2.  
  3. Seventh Attempt: Using a Lambda Expression

Blocks can be used to improve designs and enhance expressiveness.

  1. - (NSArray *)findProducts:(NSArray *)products byBlock:(BOOL (^)())block
  2. {
  3. NSMutableArray *list = [@[] mutableCopy];
  4. for (Product *product in products) {
  5. if (block(product)) {
  6. [list addObject:product];
  7. }
  8. }
  9. return list;
  10. }

The code now looks like this

  1. [self findProducts:_products byBlock:^BOOL(id p) { return [p color] == RED;}];

Construct DSL and reuse these blocks

  1. ProductSpecBlock color(ProductColor color)
  2. {
  3. return ^BOOL(id p) { return [p color] == color;};
  4. }
  5. ProductSpecBlock weightBelow( float limit)
  6. {
  7. return ^BOOL(id p) { return [p weight] < limit;};
  8. }
  9. - ( void )test7_2
  10. {
  11. [self findProducts:_products byBlock:color(RED)];
  12. }
  13.  
  14. Eighth attempt: Using NSPredicate

You can also use the standard library

  1. [self.products filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@ "weight > 10" ]];

Finish

That's all for today's coding. This article was originally written by Horance, and I will implement it using OC. If we are not iOS Developers, there are still other attempts, such as generics.

<<:  These ten IT jobs are the easiest. Where do you rank?

>>:  Why I don't like working at a mainstream tech company

Recommend

ExoPlayer supports multiple media formats and streaming protocols

ExoPlayer Introduction ExoPlayer is an open sourc...

10 insights behind the 120,000 yuan marketing campaign

Holiday campaigns have always been the focus of b...

How to use advertising materials correctly and optimize Facebook ads

Today I will show you this issue of Facebook'...

How to use coupons for promotion, here are 4 tips for you!

Coupons are the most commonly used tool in our op...

Give technical staff some non-technical advice

[[233659]] I have been working in IT for many yea...

Xiaohongshu blogger’s guide to making money on Double Eleven!

The National Day holiday is over, and Double 11 i...

How to promote APP on campus by doing activities

1. There are three common activities: booth activ...