Joshcodes
Joshcodes

Reputation: 8873

Implementing a protocol specific method in Objective-C

Given an Objective-C class MyClass which communicates with an API/Datalayer/external-something via a class Connection, MyClass should either (depending on surrounding arch) receive and instance of Connection in the constructor or an instance of Connection should be passed to each method call which requires a connection. This way MyClass can encapsulate application logic and remain ignorant of the connection details.

To reuse the logic in MyClass with different external components or to unit-test MyClass, it is proper to use a protocol (ConnectionProtocol) instead of a class Connection. This way, different classes (say ConnectionHttp, ConnnectionAPI2, ConnectionTestingEmulateProxyIssue, for instance) can be used with MyClass as long as they implement ConnectionProtocol.

Now, assume the developer has a block of code which is repeated in MyClass, MyOtherClass, and which performs a series of operations with ConnectionProtocol. This violates the DRY principle and is therefore unacceptable.

  1. Since MyClass, MyOtherClass, and do not share a common base class (other than NSObject), the developer cannot simply merge the functionallity into a base class.

  2. Adding a method to ConnectionProtocol violates DRY again as the functionality needs to be repeated in each implementation of ConnectionProtocol.

  3. Making the method an @optional member of ConnectionProtocol still does not work because

    • the method is used frequently and will need to be implemented in each implementation and
    • the method logic is specific to the application, not the connection violating SoC.
  4. The developer considers adding a category to NSObject which checks if the passed object implements protocol ConnectionProtocol and then performs the required operations. This has drawbacks:

    • The method space for NSObject is polluted for all objects in the files in which the category is used,
    • Errors in which the category method is invoked incorrectly are caught a runtime and the developer picked a strongly typed language for a reason
  5. ConnectionProtocol is extended using a category. This does not violate the DRY principle and respects SoC. It does however violate the Objective-C specification.

    @implementation id< ConnectionProtocol > (AppLogicMethods)
    
    -(void)specialMethod:(NSObject*)myParam
    {
        // Do Stuff  
    }
    
    @end
    

How does one go about solving this problem without violating DRY, respecting SoC, and meeting the limitations of Objective-C?

Upvotes: 2

Views: 156

Answers (2)

Rob Napier
Rob Napier

Reputation: 299275

This violates the DRY principle and is therefore unacceptable.

DRY is not an unassailable Law of Nature. It's not even a felony in most US states. It is a piece of useful design guidance and should be weighed against other useful design guidance such as simplicity and avoidance of SAAAD. I have seen many projects become far too over-complicated for fear of repeating a couple of lines of trivial code. Clarity is more important than brevity. Magic is worse than WET.

That said, DRY is a useful piece of guidance, and I think we can incorporate it appropriately here.

Now, assume the developer has a block of code which is repeated in MyClass, MyOtherClass, and which performs a series of operations with ConnectionProtocol.

This suggests that there are higher-level operations that are independent of the actual transport ("block of code which is repeated…and which performs a series of operations with ConnectionProtocol"). That suggests you're abstracting at the wrong level. You have a lower-level transport ("HTTP") and you have a higher-level thing called a "Connection" that manages that transport. So there should be a concrete class Connection that HAS-A <Transport>, and MyClass would HAS-A Connection.

That said, I would be slow to create this <Transport> protocol. I would start with the Connection class that includes the transport logic. If the only problem I had to solve was unit testing, I'd probably just subclass Connection for that rather than inject complexity. If non-test problems came up, then I would refactor to add the extra layer. Adding too many layers too early is a common way to overcomplicate a codebase, and complexity is a worse enemy of robustness than repeating yourself.

As a side note: your answer using class methods to package common code is also a very reasonable approach, and may be simpler for some uses.

Upvotes: 2

Joshcodes
Joshcodes

Reputation: 8873

A "utility class" is created into which the ConnectionProtocol and myParam are passed.

@implementation ConnectionProtocolAppLogicMethods

+(void)specialMethod:(NSObject*)myParam connection:(id<ConnectionProtocol>)connection
{
    // Do Stuff
}

@end

Upvotes: 0

Related Questions