Protocol Based Software Development
When I first started developing software, I made very limited use of protocols. I would occasionally model my software from what I’d seen Apple do with delegates, but once blocks came out I mostly abandoned delegates for callbacks, which I much prefer despite the inherent risks of reference cycles and the weak dance. Because really, why would I want to write my interface in a completely separate file when I already have a convenient “header” file right here. And yet, as I’ve continued to build software and especially as I learn more about software architecture, I find my eschewing interface files and depending on protocols more and more. Let’s just say I’ve become a convert to “protocol based software development”. What exactly do I mean by this?
Well, obviously, it means you use protocols, and I mean use them more than for just delegates. What I mean is that you abstract your interface away from your actual class definition, avoid putting anything in your interface except that it adheres to protocol x, y, etc. Essentially, you construct your interface by conforming to one or more protocols. Here are some “rules”1 for protocol based software development:
- As much as possible, you never import an interface file 2.
- I would almost say that so far as possible, never interact with classes, only interact with protocols. 3
- Dependency Injectors are the exception to above: It is their job to instantiate and “inject” the appropriate class. However, the recipient only defines the objects it works with by the protocols it adheres to and the injector provides the concrete class object.
If you think of all the objects in your app and how they relate to each other, the protocols are the “seams” between your objects. Each object only “knows” about the other objects it interacts with only by virtue of the protocols they adhere to. At first, this sounds a bit onerous to implement. At least, it would to me if I didn’t use it myself. But the benefits you get here are outstanding and once you get used to protocol-based development I think you’ll find that it actually makes development easier and quicker.
Protocols are Conceptually Easier
When you’re writing out a class interface, you’re thinking about how the class needs to work. When you’re writing a protocol, you’re defining how you want to interact with it… in fact, there may not even be an object to work with. In essense, you’re looking at the object from the viewpoint of the client rather than from the viewpoint of the provider. Why is this easier? Because it removes the mental load of having to think about how the class you’re interacting with works. Instead, you’re free to focus only one object at a time and I can’t overstate how much this helps. It makes it easier to complete your project one class at a time, fully fleshing that class out before moving on. This, in turn, makes it less likely that you’ll rewrite some code because you didn’t anticipate the needs of a client not yet defined.
Objection: “But I don’t have to think about anyting I don’t want to.” Yeah, that’s theoretically true but please don’t think about pink elephants. The very act of going into a class, even if only to edit it’s interface, carries with the the mental tax of thinking about how that class works. It’s inevitable. How often have you switched to a class to edit an interface file, only be distracted by actually editing the class itself? If you’re like me, it happens all the time… after all, you’re in there so you might as well make the class work correctly. But editing a protocol doesn’t carry this burden. It’s one step removed and it allows you to easily defer the mental load to a later time.
Single Responsibility Principle
Once you start defining protocols instead of interfaces, you’ll begin to realize you can split up areas of concern. Instead of using one protocol that does alot, use two instead 4. When defining a class, I’ve found there’s a certain incentive to make that class do a lot. I’m not sure why this is, but I’ve seen it enough times in myself and others to believe there’s incentive here. For instance, if I’m defining a
datasource for a view controller, I want to use that datasource to do everything the view controller needs. Why? Because I have to into that datasource class to edit the interface, and going into that class carries a mental tax, and if I’m in there, why not make it do everything the view controller needs it to do. But if I’m defining protocols to interact with, said mental tax doesn’t exist. Why should the protocol that defines my data source also keep track of object selection or data submission? Really, that’s a separate area of concern and could be handles by separate protocols. Yes, you can make one object that conforms to all these protocols but it’s easier and saner to create seperate objects that have clear and separate areas of responsibility. Interdependent state complexity goes down and resuability goes up.
SRP is really important. See my Architecting Complexity post about why. But the better you adhere to it, the easier you’ll be able to handle complexity in the app. 5 It decreases interdependent state in your app and makes it easier to find and fix bugs. Hell, it makes it less likely that you’ll write bugs in the first place because you’re classes will be smaller and easier to manage.
Swappable Objects & Resuability through Decoupling
Once you split up protocols into clear areas of concern, you will probably start to find that protocols can be resused in more than one place and you’ll find that you can easily switch out class objects in order to facilitate additions and/or changes in functionality. This used to be one of those things I though was “great in theory but not really practical”. The idea of swappable components sounds awesome, but in practice I found I never really did that sort of thing. That is, until I started using protocols. Turns out, the reason I never did it in practice was because my objects were too tightly coupled together. With such tight coupling, I never found an opportunity to resuse objects. Instead, I simply created a new view controller to accomplish the task I needed. Once I started using protocols more heavily, I “discovered” that I could reuse classes. I had inadvertantly abstracted them to such a degree that I didn’t need to create that “new view controller”. Instead, I just need to provide it with a different set of data source objects. This not only decreased the amount of code I had to maintain, but also increased the effieciency in which I was able to make changes. In other words, I was able to make changes to the project a lot easier, in less time, with fewer bugs.
Easier & Better Unit Testing
Don’t Unit Test classes, Unit Test protocols. It’s common to create a set of unit tests per class you write. Instead, if you create a set of unit tests per protocol, you will usually end up writing far less unit testing code and cover far more of your project. You pass into those tests each class that conforms to it, reusing the Unit Tests for multiple classes.
This does mean that your Unit Tests will need their own set of dependency injectors. But I posit this is a good thing. Abstracting object creation from the Unit Tests allow you to separate those areas of concern.
It will also help you to write better Unit Tests. Unit Tests should be concerned that the class in question performs according to the contract laid out in the interface. But too often, we hedge these tests around the actual class implementation simply because we know how it works. We are burdened with too much knowledge and we miss cases because our mind has already created the object. But when you test a protocol, you cannot be sure of any specific implementation. You’re writing your tests to the contract that the protocol states, especially because these tests could be applied to different classes. My point is you don’t know, and that forces you to consider the protocol separate from any specific implementation.
It is quite possible to define your entire application and how it works, even before you write a single line of functional code, purely by protocols. You probably won’t do that, but you could and it’s very useful. I call this “Scaffolding”. It’s an easy way to think through your app before you write it. Without an actual implementation, class and method definitions can be changed, added and removed without consequence. You can walk through use cases, test out dependencies and generally understand how your app can work before it does anything. In retrospect, this would have saved me tons of time. I’ve often had to rewrite a method and sometimes an entire class because I failed to anticipate the needs placed on it. Scaffolding can really help decrease the possiblitity of this.
Scaffolding also help you clearly state and maintain your project’s dependency graph. And there is a dependency graph in your project, whether you explicitly create one or not. Having that graph stated clearly in your code can not only go a long way toward helping you maintain it, but makes it so much easier for another developer to walk in and understand how your app works.
Scaffolding predicates decoupling. By creating your scaffolding first, you practically ensure yourself that you’ll use it simply because it’s there. You actual class definitions are now made in context of the protocols you’ve already defined. Certainly there will be refinement as you continue, but the bulk of the work of “conceptualizing” the apps functionality has been done, allowing you to focus on the actual implementation. This is a kind of “mental” decoupling that can allow you to focus on planning and implementation separately. As developers, I don’t think we realize how much mental tax there is when we’re trying to plan how the app will work while trying to implement that app at the same time. Software development is complex enough as it is, we shouldn’t be making it harder by trying to do two things at once.
Or precepts… or loosely based guidelines ↩
Yeah, well ok, in Swift this isn’t necessary. Hell, Swift doesn’t even have an interface “file” per se, which I see as a positive. But the idea of constructing your object’s interface in Swift using protocols is still valid. In fact, I look at protocols as the “de-facto” Swift interface files. ↩
The one exception I allow to this is the State Machine type. State machines are too often specific to a class to enforce this rule on them. However, even then you should probably consider whether the State Machine itself could be abstracted out into a protocol and injected back into the object that would use it. State machines can be a fantastic “configurator” for object behavior. ↩
or be dangerous and use 3! In some cases, I’ve found myself splitting a protocol into four or five. ↩
And I know what people say about preventing the proliferation of “bunnies”, but fuck bunnies (pun intended). I’ve never actually seen this in a project, where some developer was actually so overzealous in SRP that too many class objects were created. I have seen too few objects and so much interdependent state that it becomes impossible to truly understand the project. This is by far the most common problem I’ve seen in software projects and it needs to stop. Let’s see if bunnies actually become a problem before we decry SRP. ↩