Reputation: 8873
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.
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.
Adding a method to ConnectionProtocol
violates DRY again as the functionality needs to be repeated in each implementation of ConnectionProtocol
.
Making the method an @optional
member of ConnectionProtocol
still does not work because
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:
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
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
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