XpdWiki
Set your name in
UserPreferences Edit this page Referenced by
JSPWiki v2.0.52
![]() ![]() |
Warning, long detailed page with rants about EJBs, Cats and Dogs. OliBye heard this criticism today of evolutionary design: "If you have entities A, B and C. B and C are internal to A's operation, eventually as functionality is added you end up having to expose B and C." "Not true" I declared. Although it's true that people can end up exposing things, it doesn't have to be that way. Here's the concrete example I was given, and the worked through example of how to overcome the problem. At time 1:Interface Customer has a method Customer.getAddress(Address prototype) package org.xpdeveloper.tftd.psi.one; public interface Customer { /** * Get the address that matches the prototype * @param prototype * @return */ public Address getAddress(Address prototype); } Interface Address has one implementation Mail. package org.xpdeveloper.tftd.psi.one; public class MailingAddress implements Address { public MailingAddress() { } } Caller 1 always passes MailingAddress? as the Address package org.xpdeveloper.tftd.psi.one; import java.io.PrintStream; public class Mailer { public Mailer() { } public void printAddress(Customer customer, PrintStream stream) { stream.print(customer.getAddress(new MailingAddress ())); } } At time 2:The requirements change, now we need a billing address.Interface Customer doesn't change. Interface Address doesn't change it just has one more implementation (Billing). { package org.xpdeveloper.tftd.psi.two; import org.xpdeveloper.tftd.psi.one.Address; public class Billing implements Address { public Billing() { } }Caller 1 still only needs know about MailingAddress?, and doesn't need recompiling as none of it's efferent couplings have changed. Caller 2 can pass in BillingAddress? to access the new functionality. package org.xpdeveloper.tftd.psi.two; import java.io.PrintStream; import org.xpdeveloper.tftd.psi.one.Customer; public class Invoicer { public Invoicer() { } public void printAddress(Customer customer, PrintStream stream) { stream.print(customer.getAddress(new BillingAddress())); } } Implementing CustomerThe implementation of Customer isn't shown here, but it doesn't need to change either, since the specialisation of the query to extract a billing address (say from a database) as opposed to a mailing address would be implemented in the concrete Address implementation.RantI'm not really happy with this solution as it has a code smell. It's the Customer.getAddress() method. We're encouraging callers to pick us appart, if those same callers then call methods on what we've given them they break c2:LawOfDemeter![]() EnterpriseJavaBeans encourage us to write getters and setters, but as we know GettersAreEvil we should ExposeBehaviourNotState? so I'm tempted to write this all again and rant more about why EJB spell doom. TopLink? doesn't suffer from this problem. If we ExposeBehaviourNotState? and played RrC I think we'd find Customer.send(Mail) and Customer.send(Bill) which be a much better solution. You wouldn't have know upfront about different addresses, wouldn't need to use prototypes and look not a getter or setter in sight. ConclusionSo I feel I've proved that you can add functionality without the need to expose internals on the callee, or require changes to previously supported callers. Therefore it isn't an unavoidable emergent property of evolutionary delivery that internal implementation of an interface need to be exposed.I appreciate that this solution is given with knowledge that the some other sort of address would be added, but we didn't need to know what sort of address. Even in the case where the initial method was called getAddress() with no parameter, recompilation and therefore the implication of close coupling or over exposure can be avoided, by deprecating the Customer interface and making it into facade for the older client which converted the call into getAddress(new Mail()). I've used this pattern many times to support 3rd party clients how won't/can't upgrade. I agree that implementation often is exposed, but my point is, it doesn't have to. ObjectOrientation? wasn't invented because to simply have Cat and Dog inheriting from Mamal. Inheritance and Polymorphism were invented to allow encapsulation and separation of concerns, choose wisely what you use these powerful features for. If you want to know if your code is ok, download one of the many CodeMetrics? kits (such as? --MilesD) and look at the efferent and afferent coupling of your classes. XP is not just about proving things work, it's about building things that are easy to change. Try following Demeter's four simple rules of thumb (which are? --MilesD (c2:LawOfDemeter as above)) and if you don't have an "Aha" moment within 48hours post your experience here. Also see ThoughtForTheDay DoubleDispatch? for why I use the phrase "rule of thumb" instead of law. -- OliBye
|