Case study:

Suppose you now have a utility class that renders the specified area of the incoming page red

Bad design

Defines a base class BaseFlushedViewController: UIViewController, returns a flushArea, for tools for dyeing.

class FlushHelper {
    static func flush(_ viewController: BaseFlushedViewController) {
        viewController.flushArea().backgroundColor = .red
    }
}

class BaseFlushedViewController: UIViewController {
    func flushArea(a) -> UIView {
        return view
    }
}
Copy the code

The arguments we pass in must be subclasses of it, and since each page may need to refresh different views, we should theoretically rewrite its flushArea method

The problem with this is twofold:

  • A new page class might forget to override this method
  • If the required incoming page is oneUINavigatiionControllerUITabbarController? (Maybe I’ll now render the navigation bar or the bottom TAB bar), then I’ll add two more interface adaptations to the tool class. That’s obviously not what we want
class FlushHelper {
    static func flush(_ viewController: BaseFlushedViewController) {
        viewController.flushArea().backgroundColor = .red
    }
    
    static func flush(_ viewController: BaseFlushedNavViewController) {
        viewController.flushArea().backgroundColor = .red
    }
    
    static func flush(_ viewController: BaseFlushedTabViewController) {
        viewController.flushArea().backgroundColor = .red
    }
}

class BaseFlushedViewController: UIViewController {
    func flushArea(a) -> UIView {
        return view
    }
}

class BaseFlushedNavViewController: UINavigationController {
    func flushArea(a) -> UIView {
        return view
    }
}

class BaseFlushedTabViewController: UITabBarController {
    func flushArea(a) -> UIView {
        return tabBar
    }
}
Copy the code

Design of face interface

Define a protocol

protocol Flushable {
    func flushArea(a) -> UIView
}

class FlushHelper {
    static func flush(_ viewController: UIViewController & Flushable) {
        viewController.flushArea().backgroundColor = .red
    }
}

class SomeViewController: UIViewController.Flushable {
    func flushArea(a) -> UIView {
        return view
    }
}

class SomeNavViewController: UINavigationController.Flushable {
    func flushArea(a) -> UIView {
        return navigationBar
    }
}

class SomeFlushedTabViewController: UITabBarController.Flushable {
    func flushArea(a) -> UIView {
        return tabBar
    }
}
Copy the code

Unify the interface of the utility class into UIViewController & Flushable

The benefits of this:

  • When you call the utility class interface, you know exactly what the incoming page is to implementflushAreaMethod, do not forget to rewrite the case mentioned above after inheritance
  • Fit allUIViewControllerAnd subclasses. There is no need for utility classes to open additional interfaces

To compare

It looks like an interface oriented approach is just one interface in a utility class compared to one that uses a base class. But in fact, an interface method, SomeNavViewController and SomeFlushedTabViewController are a special case of the UIViewController. In the Base class implementation, the three Base classes are flat Base classes that cover all page types.

Suppose that if there is a new navigation page, then the base-oriented method needs to add a new base class to cover this special case, whereas the interface-oriented method only needs to extend a new type, and the utility class interface does not need to be added.

The above idea is an embodiment of the open and closed principle in object-oriented design rules (if there are mistakes welcome to correct).