The introduction

For programming to an interface, has introduced in many articles, are in a very simple things as a general introduction, such as transportation and animals, to the benefits of using an interface is discussed and how to use the interface, these examples not only too simple, and almost useless in actual project development, in addition to introduce theory of universal concept, Therefore, this paper tries to discuss interface – oriented programming from the development scenario of an enterprise – level project.

The essence of an interface is the separation of definition and implementation. Many programming languages have keywords that describe interfaces, such as protocol in Swift/OC, interface in Java, Kotlin, and C#. In most programming languages, the interface keyword is interface, but the interface keyword in OC modifies the class. Interface is defined in the header file and implementation is used in the implementation file. The definition and implementation are also separated. Moreover, method calls are actually run-time messages that can easily change the implementation, providing a deeper separation. In a specific programming language, an interface does not have to be a fixed type of keyword declaration, as long as it conforms to the idea of the interface.

In an object-oriented system, the function of the system is made up of many different object collaboration, small to different class, big to different application, the interactions between them is particularly important to an interface programming is in order to better implement these interactions, so the programming to an interface is not parallel to object-oriented programming, or advanced concepts, It is the practice of object-oriented programming, part of object-oriented programming.

Several objects are combined into a collection through some correlation, which is often called a module. On modular, there are several similar terms, such as groupware, plug-ins, and they are very similar concepts, are the basis of partition, modular focus on reuse, componentized decoupling, pay more attention to the plugin emphasizes independent operation, in this paper, the modular is a broad concept, should accord with the partition of thought can be summarized as modular.

For a slightly large-scale enterprise application, the business is generally more complex, modular development is an inevitable choice. The following content will take iOS development as the background, from the interaction between modules, to the interaction of objects in the module to the two aspects of application interface programming methods.

Interactions between modules

In the outline design stage, we generally design the project structure, and divide the modules vertically and horizontally according to different factors. Vertically, the modules are divided according to the strength of business attributes. The stronger the business attributes are, the higher the level is.

Interaction between vertical modules

According to the strength of module business attributes, it can be roughly divided into three layers: basic layer (no business attributes), general (business) layer (low business attributes) and business layer (full business attributes), as shown in the figure below.

The business layer refers to the general layer, and the general layer refers to the base layer. Try not to refer to the concrete implementation class, but to an interface or abstract class.

Take an office application similar to Dingding as an example. The business layer has three main modules: message, document and address book. Each module uses user information, and there are three application scenarios:

  • The message module uses the user’s basic information group to compose groups.
  • The document and workbench modules operate differently with user permissions;
  • The address book uses various organizational information associated with users to belong to their respective organizational structures.

The user information module is the general layer module, message, document, address book module is the business layer module, the business layer uses the user information module information reference interface.

Protocol UserItemProtocol {// Basic user information var userId:String{get} var name:String{get} var avatarUrl:String? {get}} enum FilePermission {// FilePermission enumeration case read, create, write, Share} protocol UserPermissionProtocol {// user permission var userId:String{get} func hasPermission(to Permission: [FilePermission]) - > Bool} protocol organizationLinkedListProtocol {/ / tissue interface list var orgId: String var {get} name:String {get} var parent: organizationLinkedProtocol? {get}} protocol UserOrganizationProtocol {// User organization var userId:String{get} var title:String {get} var organization:organizationLinkedListProtocol{get} }Copy the code

Four protocols are defined above, which correspond to the three application scenarios of service modules:

  • In the message module, the group contains a list of members that can be defined as an array such as [UserItemProtocol];
  • Document module, query the user has the authority to read documents, whether can access UserPermissionProtocol. HasPermission (to: read);
  • Address book module, query a user in the tissue of the current organization structure information, visit UserOragnizationProtocol.org anization, according to the linked list node can be read one by one.

Specific implementation of the Protocol above may be the same object, as follows, but we can not provide a huge Protocol, meet the use of one interface to complete one thing.

Struct UserInfo: UserItemProtocol, UserPermissionProtocol, UserOrganizationProtocol {// All user information attributes. }Copy the code

Longitudinal module of the interaction between the basic principle is: high level modules should not depend on low-level modules directly, they should depend on abstract, high-level modules only know interface provides the ability of concrete implementation is in low level modules, should as far as possible concise and interface, as long as meet the demand of high level modules can, do not add unnecessary methods or properties.

Interactions between horizontal modules

In vertical division, the interaction between high-level and low-level modules can rely on its abstraction, but the interaction between horizontally divided modules in the same level usually needs a middleman to transfer information, the middleman is the communication system between modules, we usually use the routing system to achieve.

The routing system is used for communication between modules. There is no coupling between modules, and the application is simple. It can also take into account the synchronization between multiple client routes, which is a common choice for page skipping. Page hopping is also a special kind of communication. Generally, only input parameters are required, and the requirements for returned data are not strong. As a result, the general routing system has weak data interaction ability.

The routing system imitates the way of HTTP request to realize the communication between modules. By registering routes and sending routing requests and parameters, the required communication data can be obtained synchronously or asynchronously. The “page” in “page Jump” is also a kind of communication data, such as UIViewController.

Enumerates the functions of the routing system

  • Registered routing address;
  • Can handle a particular route call;
  • Can initiate routing requests;
  • Synchronous/asynchronous return communication data;

Define relevant interfaces based on the above functions

Protocol RouterHandlerProtocol {// Func handleRouter(url:string, params:[string :Any]? , completion:((RouterResponseProtocol?) -> Void)?) } protocol RouterResponseProtocol {var data:Any? {get set} var error:Error? {get set} }Copy the code

Implement the routing processing center

public class RouterCenter { func register(url:String, Handler: RouterHandlerProtocol) {/ / registered routing handler / / record the routing handler} func starRequest (_ the request: RouterRequestProtocol) {/ / processing route requests / / query routing handlers = > RouterHandlerProtocol / / query to RouterHandlerProtocol handleRouter (url:, params:, completion:); }}Copy the code

This is the internal processing of a routing processor, acting as the middle man, and then implementing the originator and receiver with a practical example.

For example, in the nail, the address book page needs to display the latest contacts. The address book page belongs to the address book module, and the latest contacts belong to the message module. Both of them belong to the service module and interact with each other through the routing system.

  1. The message module registers and implements the routing protocol RouterHandlerProtocol, which is implemented by the class MessageService and defines the route value:native://message/last_contacts

Register route:

RouterCenter.register(url:"native://message/last_contacts", handler:MessageService.shared);
Copy the code

To implement a route handler, return data that implements the RouterResponseProtocol:

class MessageService { var lastContactItem:[UserItemProtocol]? } extension MessageService : RouterHandlerProtocol { func handleRouter(url:string, params:[String:Any]? , completion:((RouterResponseProtocol?) -> Void)) { if url == "native://message/last_contacts" { var response = RouterResponse() response.data = Self. lastContactItem // Return data completion? (response) } } }Copy the code
  1. The address book module is the sender of the routing request. It needs to implement the RouterRequestProtocol (of course, it can provide a default routing request class in the routing system, such as RouterRequest) and then send the request.
var request = RouterRequest(url:"native://message/last_contacts") request.responseClosure = { response in if let lastContacts = response.data as? [UserItemProtocol] {/ / a recently used routing}} RouterCenter. Default. StarRequest (request);Copy the code

For this particular form of communication, UIViewController can be treated as data in the RouterResponseProtocol, and the originator can decide how to open the new page.

In this case, the address book and message module is the same level of horizontal partitioning module, routing system module is a common layer module, module tree cannot depend on each other, only by the routing system transfer information, contacts and message module rely on routing module in the right by request and routing protocols described in the abstract, The protocols are RouterHandlerProtocol, RouterResponseProtocol, and RouterRequestProtocol. The diagram below:

Interaction of objects within a module

At the base level, there are typically pure logic operations modules, or pure UI modules, where each class and even each method can be used separately. For example, system libraries SUCH as UIKit.framework and Foundation. Framework generally do not interact with each other within them.

In the general layer, business attributes are weak. The routing system module in the previous section has a single function, and internal objects generally rely on each other directly.

In the business layer, it is much complicated, may contain both UI also contains the logic operations, its internal object interaction cannot rely on the directly, otherwise it may make a dramatic increase in the coupling, is not conducive to the follow-up function extension, this time we can introduce architectural patterns, implementation architecture model role in the interaction between the module object interaction.

Common architectural patterns in iOS applications include MVC, MVP, MVVM, and VIPER from clean Architecture. VIPER mode has clear role positioning and follows the principle of single responsibility. Interactions between roles are mostly realized through interfaces. Therefore, this paper takes VIPER mode as an example.

VIPER is called view-interactor-presenter-entity-router.

In VIPER, the ViewController is categorized as a View role and is only responsible for displaying logic. Interactor is responsible for business logic, including network requests, file access. Presenter connects the View to the Interactor. Entity refers to the original data and has no business logic. Only Interactor can access it. Router refers to the route jump and focuses on the interaction between modules.

Take the contact list in the nail address book module as an example, there are two requirement scenarios: display the list of contacts, create a group chat and add members; Enumerate specific functions according to requirements:

  1. In the personnel list, each line displays the basic information of the contact, including name, department and position;
  2. A person has two statuses: normal status (display of contacts) and selected status (create a group chat and add multiple members).
  3. In normal state, click to jump to personnel details page;
  4. Select the status, click to select the personnel information or deselect the personnel information, and finally return the selected personnel information to create group chat service through a “OK” button.
  • Interactor role implementation
Protocol ContactItemProtocol {// Contact information var userId:String {get} var displayName:String {get} var Avatar :String {get}} Protocol ContactListInteractorProtocol {/ / item display processing interface var items: [ContactListItemProtocol]? {get} func requestContacts() } protocol ContactListInteractorOutputProtocol { func contactDidReceived(_ items:[ContactItemProtocol]? , error:Error?) }Copy the code

Implement the main classes: ContactListInteractorProtocol ContactListInteractor, follow, holding an external agent output, is used to notify the external data has been updated, usually the output by the presenter implementations, This is also mentioned in the definition of presenter.

class ContactListInteractor : ContactListInteractorProtocol { weak var output : ContactListInteractorOutputProtocol? var items:[ContactItemProtocol]? Func requestContacts () / / self synchronous/asynchronous request search data. The output contactDidReceived (users, error: nil)}}Copy the code

Here we will implement only the list display of data for the time being, leaving the selection state to be dealt with later. In addition, the Entity role is a structure or class that implements the ContactItemProtocol, so the implementation is skipped here.

  • View role implementation
Protocol ContactCellModelProtocol{// Display data interface for each line var imageUrl:String{get} var nameString:String{get} var titleString:String? {get} } protocol ContactListViewDataSource : Func numberOfRows() -> Int func cellModel(at row:Int) -> ContactCellModelProtocol} protocol ContactListViewEventHandler : Func selectRow(at index:Int)} protocol ContactListViewProtocol {func reloadData()}Copy the code

Implement View main classes: ContactListViewProtocol ContactListViewController, follow, holding a dataSource for displaying data, hold eventHandler for the click event processing, In general both dataSource and eventHandler are implemented by Presenters.

class ContactListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, ContactListViewProtocol {/ / var dataSource ContactListViewProtocol agreement: ContactListViewDataSource? var eventHandler:ContactListViewEventHandler? }Copy the code
  • Presenter role implementation

Presenter used to connect to the View and Interactor, View the role of the callback ContactListViewDataSource and ContactListViewEventHandler, And then hold a ContactViewProtocol; Implement callback ContactListInteractorOutput Interactor roles, and then hold a ContactListInteractorProtocol.

class ContactPresenter: ContactListViewDataSource, ContactListViewEventHandler, ContactListInteractorOutput { var view : ContactViewProtocol! var interactor : ContactListInteractorProtocol! var eventHandler : ContactListEventHandlerPresenter? func selectRow(at row: Int) { let user = self.interactor.results[row]; ContactRouter.gotoUser(user.userId); }}Copy the code
  • Router Role Implementation

Jump to a page in another module by applying the routing system from the previous section

struct ContactRouter { func gotoUser(_ userId:String) { var request = RouterRequest(url:"native://contact/user/\(userId)") request.responseClosure = { response in if let vc = response.data as? UIViewController { currentVC.pushViewController(vc, animated:true); } RouterCenter.default.starRequest(request); }}Copy the code

The above is a standard VIPER mode implementation module, and the class diagram is as follows:

And then to implement the selection state, in the selection state, when the display needs to indicate whether a contact is selected, click the logic is inconsistent, this mainly refers to the need to modify Interactor, Presenter and View these three roles.

  • Interactor role, add 2 interfaces:
protocol ContactPickerInteractorProtocol { func pickStatusDidChanged(at index:Int) } protocol ContactPickerOutputProtocol { var results:[ContactItemProtocol]? {get} func pick(result:ContactItemProtocol) } class ContactPickerInteractor : ContactPickerOutputProtocol { weak var output: ContactPickerOutputProtocol? }Copy the code
  • To modify the Presenter role, click the Proxy interface
class ContactPresenter: ContactListViewDataSource, ContactListViewEventHandler, ContactListInteractorOutput { var view : ContactViewProtocol! var interactor : ContactListInteractorProtocol! var eventHandler : ContactListEventHandlerPresenter? func selectRow(at row: Int) { let user = self.interactor.results[row]; self.eventHandler.selectUser(user) } } protocol ContactListEventHandlerPresenter { func selectUser(_ user:ContactItemProtocol) } class ContactNormalEventHandlerPresenter : ContactListEventHandlerPresenter {/ / ordinary click func selectUser (_ user: ContactItemProtocol) ContactRouter.. gotoUse(user.userId) } } class ContactPickerEventHandlerPresenter : ContactListEventHandlerPresenter {/ / select contacts var pickInteractor: ContactPickerInteractor! func selectUser(_ user:ContactItemProtocol) pickInteractor.pick(user) } }Copy the code
  • View to modify the ContactCellModelProtocol interface
Protocol ContactCellModelProtocol{// Display data interface for each line var imageUrl:String{get} var nameString:String{get} var titleString:String? {get} var picked:Bool? {get} }Copy the code

Finally, to connect the VIPER roles, we need a first push, for which we can create a role ModuleFactory as follows:

Struct ContactModuleFactory: RouterHandlerProtocol{static func handleRouter(url: String, params:[string :Any]? , completion:((RouterResponseProtocol?) -> Void)) -> UIViewController? {if / / ordinary list to show the return ContactModuleFactory. BuildNormalContact () else if / / option to show the return ContactModuleFactory.buildMultiplePickerContact() } static func buildNormalContact() -> UIViewController{ let vc = ContactListViewController() let interactor = ContactListInteractor() let presenter = ContactPresenter() vc.dataSource = presenter vc.eventHandler = presenter interactor.output = presenter; let eventHandler = ContactNormalEventHandlerPresenter() presenter.eventHandler = eventHandler } static func BuildMultiplePickerContact () - > UIViewController {/ / analogy buildNormalContact}}Copy the code

Through VIPER architecture mode, each role in interface connection mode is used respectively, which greatly reduces the coupling between objects, and the reuse degree of each role object is also very high, which can be arbitrarily combined into a new module.

conclusion

The above describes how to use modular thinking to realize interface programming in an enterprise iOS application from both inside and outside the module. When realizing a module or an object, it is necessary to start from defining the interface, determine the boundary of their functions, and develop rules for interaction. However, it is not necessary to define an interface for every class, only when there are extensibility or decoupling requirements for the class.

Wechat public account to synchronize the article address