Reusability and Composition in Swift

The one concept Nietzsche, Thoreau and Hesse most probably tried to elude through their lives: Dependency. Even if one does not agree or embrace their philosophies, a programmer should — or must? — apply their way of thinking while programming.

Let’s define the problem first:

Coupling

Imagine a car which does not a have a single piece that can be detached. From seats to wheels, chassis to sun roof are designedas a single piece. If I get a flat tire because of some nasty nail laying on the high road, I cannot just buy a new tire, I would need to buy a whole new car. So we would call this design a coupled design or in a more sincere and less polite manner; an idiotic design. Programming is no different. If a change in one class or function requires me to refactor many other parts of the program, then I have designed the undetachable car. Not cool. Not cool at all.

So what do we do when we realize that our code is tightly coupled? We take a moment of silence.

We realize we wrote some poor code and take an oath to not do that again. Live by these words: Encapsulation , Single Responsibility and Composition over Inheritence . I am going to start with some simple examples of how we can honor these concepts within our program and then build up from it into rather (but not really) complex examples.

Pianist Or the Violinist

Your function signature should do exactly what it claims to do; not one bit more or less. Let it be the pianist or the violinist, not both.

So I have a function which is supposed to print the number of certain characters in the string:

func printNumber(of char: Character, in string: String?) {
  if let string = string {
    var count = 0
    for s in string {
        if s == char { count += 1 }
    }
  }
  print(count)
}

This function does less than it promises. In the case of a null string, nothing is printed while it should print “0”. I could solve this by unwrapping the string before I feed it to the function, though it would be against encapsulation for I act by knowing (or assuming) the implementation details of the function.

Here is a function which does more than it should:

func removeNils(from strings: [String?]) -> [String] {
  return strings.flatMap { $0 }.filter { !$0.isEmpty }
}

It is named removeNils though it also removes the empty strings from the String Array. Even if the caller of this function is happy about this, it can cause errors or unintended behaviour.

Strongest Man Functions

Functions are like our good friend Frodo, they are already bearing a great burden, they should not bear any more than that. Suppose you are in need of validating the input in text fields in order to activate the continue button. And the validateInputs function validates user email and user password using regex. The obvious correct way of achieving this would be to have two other functions to help validateInputs lift the weight: validateEmail and validatePassword could take the implementation of those separable logics out of validateInputs . This way, validateInputs would have much shorter and easy-to-read implementation, and we can reuse email and password validating functions within the rest of our program. Although we love to code, we try not to write so much code :] It is like recycling: reuse what you can. Though if attempt to reuse makes you write more code, you are either pushing it too far — trying to use a hammer to pull of a nail — or you did not write reusable code in the first place.

Temptation of Being Specific

From UI components to logic layers, try to design everything by being as little specific as possible; like this ugly little guy on the left. As programmers, we love that ugly colorless dude. I can transform him into almost anything by adding features. In contrast, the fancy jerk on the right can only be useful when I need a bird with a purple neck, light blue belly and so on. I have to override his half blue half gray head in order to have a bird with red head. We can relate to Decorator Pattern on this one. Even if we need a fancy bird right now, to be able to create less fancy birds or different sort of fancy birds in the future, we should first build the ugly bird and then add features to it.

I think it is time to stop with the literature and show some Swift .

Why Not Inheritance?

The above problem can also be solved by inheritence as the following:

class Bird {
  func fly() {
    // Implementation
  }
}
 
class Finch: Bird {
  func showOff() {
    // Implementation
  }
}

Nothing seems to be problematic here. Finches are birds and they can fly. In addition to that, they can show off their fancy feathers. If I need a Gouldian Finch , I can inherit from the Finch class. Herein the problem lies: What if I need a Canary class. Though they share a common ancestor, they are only cousins (I am not an ornithologist so forgive me if one is ancestor of the other, just act like they are not, for me ❤). So I can only inherit from the Bird class. When I have many cases like this, I will end up with a tree wider and more complex than the House of Targaryan’s Family Tree . The solution to this is to favor composition over inheritance when we can and of course where it makes sense.

Composition

Solution to the same problem with composition:

protocol Avian {
  func fly()
}
extension Avian {
  func fly() {
    // Default implementation if it is useful
  }
}
protocol FlameBreather {
  func flameBreath()
}
extension FlameBreather {
  func flameBreath() {
    // Default implementation if it is useful
  }
}

Now to create a Dragon class, I will use above protocols.

class Dragon: Avian, FlameBreather {
  // Implementation
}

Instead of making our Dragon a bird, we gave it the necessary functionality with a protocol. And what’s even better is, if the requirements change in the future, we can remove functionality just as we detach lego parts; we will probably still need some refactoring but relative to inheritance, it’s like shooting fish in a barrel. As we extend our toolbox with more protocols, we will be able to compose Parrots , Pterosaurs and Nazgul Mounts without any additional implementation.

Dependency Injection for View Controllers

Any separable logic, feature or functionality deserve its own protocol if there is even a slight chance of a need to reuse in the future. The data provider of your view controller whether it is a view model or an interactor, should be defined as a protocol instead of a concrete type. And as long as the data provider conforms to what view controller expects, we should not care about the concrete type.

This is the monthly listener screen of Spotify . For the sake of simplicity, ignore everything below MONTHLY LISTENERS text. Here we have bunch of images that can be swiped, a large label and a sort of subtitle label. In this case, our view controller would require the images and the text to show on the screen. Instead of calling this view ListenersViewController , I will call it SwipableImagesViewController . My controller should demand the required data and do not care about anything beyond that.

protocol DataProvider {
  var imageUrls: [URL] { get }
  var title: String? { get }
  var description: String? { get }
}

And the view controller:

class SwipableImagesViewController: UIViewController {
  var dataProvider: DataProvider!
  // Add the images to collection view and assign texts
  // ...
}

When I initialize the view controller, I must instantiate the data provider right away with my desired concrete type.

let viewController = SwipableImagesViewController() 
viewController.dataProvider = ArtistDataProvider()

Now I can use a different data provider which maybe makes service calls to different endpoints and so on. If I want to disable the swipe behaviour for some screens maybe for unpaid users or something, I can add that flag to my protocol.

protocol DataProvider {
  //...
  var isSwipable: Bool
}

In the controller:

class SwipableImagesViewController: UIViewController {
  //... 
  override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.isUserInteractionEnabled = dataProvider.isSwipable
  }
}

With the same manner, you can make your view controller more and more reusable. Just beware of going for extremes. You don’t want to push it too far since it may be an overkill. I personally like to cover highly similar UI’s with maybe few differences just as an extra button or some extra functionality. But the mindset while naming and designing you controllers, I have found to be useful in every scenario. Thinking “does my controller really need this property?” pushes me to design simpler interfaces and as a result, write cleaner code.

Single Responsiblity, Composition and Dependency Injection

  • Things should have one purpose and one purpose only.
  • Prefer has a relationship instead of is a relationship (conforming to protocols is not exactly a has a relationship since we do not retain or own any object, but the perspective is the same.). Inheritance can easily get out of control and increase the complexity instead of reducing it.
  • Work with interfaces (protocols) instead of concrete types. It grants you flexibility while increasing reusability.

A simple and independent mind does not toil at the bidding of any prince. — Henry David Thoreau

稿源:Swift Post (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » Reusability and Composition in Swift

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录