• Post Reply Bookmark Topic Watch Topic
  • New Topic

Question about class and interface inheritance.  RSS feed

 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I've found that I tend to use interface inheritance far more frequently, especially now that I am trying to make more use of design patterns. But I have a question I'm not quite clear on. Here is an attempt at asking it, hopefully with not too poor an example.

I could do this:



Or I could do this



In the latter case, I have code that is duplicated in the two implementations of cookie. Which worries me, since I could find and fix a bug in one and not the other.

What does one usually do when implementations of an interfare share common functions? My tendency is to make a CookieTasks class for the common methods. I'm not sure that doesn't create the same problems as class inheritance, if I change a CookieTask, I need to make sure I don't break any classes that use it. But I was brought up on the idea that not reusing code is a sin.

How much should I worry about reusing code when implementing interfaces?
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
In this particular example, the correct pattern would probably be the 'Decorator'. The cookie is a lone class, and the ingredients are things that the cookie uses to modify itself. For example you have something like this:
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And to your point about code re-use - the decorator pattern allows you to do this nicely. But wait - you may ask - didn't you duplicate the addIngredient(new Milk(...)) and addIngredient(new Egg(...)) lines? True, I did, but you could refactor them into a reusable method, like so:


It also lets you make a vegan cookie which can't have eggs or milk without changing the class structure - just swap out the ingredients.
 
dennis deems
Ranch Hand
Posts: 808
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steve Luke wrote:In this particular example, the correct pattern would probably be the 'Decorator'. The cookie is a lone class, and the ingredients are things that the cookie uses to modify itself.

I disagree. The way I see it, Cookie is really an abstract concept that can be implemented a number of different ways. Oatmeal is one way of being a cookie, and Butter is another, very different way of being a cookie. There are myriad other, completely different ways of being a cookie: NoBake, Bar, Sandwich, Fortune. In contrast to the coffee example in HFDP, it seems to me there really isn't anything here to decorate. In that example, an undecorated object has utility -- it's a cup of coffee, and we can drink it. But here, without any decorators there is no cookie.
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Other than the fact the implementation described above isn't really a decorator pattern (the traditional decorator uses wrappers and such, so an OatmealDecorator would take an instance of Cookie and add to it some change, rather than being passed to the Cookie to be acted on by the cookie): I still thing the decorator pattern (in its correct form) or the composition that I did above (and incorrectly called the decorator - don't know of the correct pattern name) both are more appropriate than making subclasses of Cookie.

The problem you mention (Dennis) about having many different ways of 'being' a Cookie is exactly the problem that both the composition technique and the decorator pattern address. There are many different, and independent ways the Cookie can be 'extended' and really there is little chance of predicting all possible combinations at initial development. You can have OatmealCookie, which would presumably extend Cookie. And OatmealRaisinCookie which extends OatmealCookie. You can have a CookieBar, and a NoBakeCookie. How about a NoBakeOatmealRaisinCookieBar? Trivial in terms of decorator or the composition, painful if you rely on inheritance.

You might have a hard time conceptualizing an undecorated Cookie, but that does not detract from the correctness of the pattern - in my opinion anyways.

Dennis Deems wrote:
Steve Luke wrote:In this particular example, the correct pattern would probably be the 'Decorator'. The cookie is a lone class, and the ingredients are things that the cookie uses to modify itself.

I disagree. The way I see it, Cookie is really an abstract concept that can be implemented a number of different ways. Oatmeal is one way of being a cookie, and Butter is another, very different way of being a cookie. There are myriad other, completely different ways of being a cookie: NoBake, Bar, Sandwich, Fortune. In contrast to the coffee example in HFDP, it seems to me there really isn't anything here to decorate. In that example, an undecorated object has utility -- it's a cup of coffee, and we can drink it. But here, without any decorators there is no cookie.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thank you both. The discussion was educational. It's always helpful to see how people with more experience think about patterns.

Having attempted to digest the discussion, I think I may not have provided the best example. The methods that I actually call are really more a matter of calculating properties based on a set of values contained in the class. In that case, I don't think you could say they all stem from the same base class. Maybe the classic example of getting the area of a 2D shape versus a 3D shape might be better. Or in my case, you might calculate the volume of a simple shape based on its dimensions, whereas for a more complex shape, you might calculate the volume based on the amount of water it displaced. The density would be calculated identically in either case as mass divided by volume.

I can see where this would be constructed using inheritance (either through an object base class or an interface). I am having more trouble seeing it constructed using composition. I'm not liking the fact that in my simple implementations, density gets coded twice or dumped into a helper class when I use an interface, since the real calculations are a bit more complex. I'm not sure you haven't provided me a way to refactor the code to solve the duplication in another way, but I'm not quite seeing what that would look like.
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yes, that is a much different situation where you have a limited or predictable number of implementations. It is not uncommon in these situations to use an interface with an abstract class with partial implementation, then have your concrete types extend the abstract class (a lot of the Java Collections Framework is built this way). The abstract base class is used to provide common code, but does not influence the relationship between classes - if something needs a different parent class they can implement the interface without extending the abstract class.

And if you are extends-adverse, you could talk about having a DensityCalculator and VolumeCalculator that you inject into class to do the work. I don't think it is necessary in the example you provide but is an approach to think about if the situation seems too complex for a simple parent/child heirarchy. There are a number of patterns that could be looked at to help influence your design: Inversion of Control / Dependency Injection, Visitor pattern.
 
dennis deems
Ranch Hand
Posts: 808
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steve Luke wrote:Other than the fact the implementation described above isn't really a decorator pattern (the traditional decorator uses wrappers and such, so an OatmealDecorator would take an instance of Cookie and add to it some change, rather than being passed to the Cookie to be acted on by the cookie): I still thing the decorator pattern (in its correct form) or the composition that I did above (and incorrectly called the decorator - don't know of the correct pattern name) both are more appropriate than making subclasses of Cookie.

The problem you mention (Dennis) about having many different ways of 'being' a Cookie is exactly the problem that both the composition technique and the decorator pattern address. There are many different, and independent ways the Cookie can be 'extended' and really there is little chance of predicting all possible combinations at initial development. You can have OatmealCookie, which would presumably extend Cookie. And OatmealRaisinCookie which extends OatmealCookie. You can have a CookieBar, and a NoBakeCookie. How about a NoBakeOatmealRaisinCookieBar? Trivial in terms of decorator or the composition, painful if you rely on inheritance.

You might have a hard time conceptualizing an undecorated Cookie, but that does not detract from the correctness of the pattern - in my opinion anyways.

Dennis Deems wrote:
Steve Luke wrote:In this particular example, the correct pattern would probably be the 'Decorator'. The cookie is a lone class, and the ingredients are things that the cookie uses to modify itself.

I disagree. The way I see it, Cookie is really an abstract concept that can be implemented a number of different ways. Oatmeal is one way of being a cookie, and Butter is another, very different way of being a cookie. There are myriad other, completely different ways of being a cookie: NoBake, Bar, Sandwich, Fortune. In contrast to the coffee example in HFDP, it seems to me there really isn't anything here to decorate. In that example, an undecorated object has utility -- it's a cup of coffee, and we can drink it. But here, without any decorators there is no cookie.


Well the "correct pattern" really depends on what we need to DO with a cookie. If all we need is for them to sit on a tray looking lovely for the guests, then by all means decorate away. But if we are modelling the process of preparing a cookie, maybe for some recipe management application, then it's really not going to work just to say, "whatever varies from a base cookie we'll add as a Decorator".
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks all,

I'm really only extends-averse from reading posts that warn of the dangers related to maintainability when you extend.

I've hit one other conceptual snag with interface. I'd like to have all the calculations that are required to use an implementation of the interface included in my interface definition. I've got one method that may or may not need an argument, depending on how the interface is implemented. I can write methods for the classes that implement the interface, and not include the method in the interface. My concern is that someone might make another implementation later on and not know that the method is needed (not being in the interface). This is a really simple example-



It sounds like maybe using an interface with an abstract class might alleviate this concern. I've also considered just passing null when the argument was not needed, but that sounds like a kludge and confusing to anyone reading the code.

Am I on the right track thinking about this?
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah, there you are starting to get into some of the more difficult situations.

The interface should define a method for volume() if it expects implementors to have it - both because there is no way to enforce the contract without defining the method in the interface, and no way to call the method from the interface (you would have to cast to the specific type which is no good).

Conceptually (to me) the calculateVolume() and calculateVolume(double) methods are two different methods, and I would tend to treat them as that, and would have them as two seperate methods in the interface. Then you get to the situation where some implementations use one method, and other implementations use the other. The way the Java Collections Framework handles these situations is to create all the method signatures in the interface, call some of them 'optional' then have the ones that aren't implemented throw UnsupportedOperationException. You could have a base implementation which throws the exception for both methods, then subclasses would extend the base class and override one or both methods as appropriate. I don't necessarily think I like this idea but it is an implementation with precedent in core Java (not that that makes it perfect).

The idea of always requiring the parameter is easy to do, but could cause confusion about what should be passed when you have a situation when you don't need it. An idea to get around that would be to have a special signal in the interface which is used when the parameter is not needed. Something like:



I think this is the situation where Visitor pattern could help*. You would have a VolumeCalculator interface like this:



You then have a simple implementation (not shown) and a complex implementation like this:


You Something's interface would change to:



* I use pattern names because they are good places to search and get ideas from, not because they exactly match your situation, which as Dennis suggests we don't quite know enough to be sure if this is the 'correct' way of doing things.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
After a few false starts, I implemented the visitor pattern as follows:



Have I got the concept correct?

It seems like it will be quite helpful for what I am trying to do. The actual problem is a bit more complex- in the soft container case, there are two objects that might be set, either of which might set the volume. But it feels like you have set me on the right track. I suppose I should also have an abstract class so I don't duplicate the density method or just call a static method. Thanks for all the help. Let me know if I've understood what you suggested.
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
There are a couple of things that I would change in your implementation. First, you have SoftContainer and HardContainer both do exactly the same thing. Either there should be a common abstract base class with the shared behavior in it, or have only one class. Probably the first.

The second thing to change is the contents. The contents probably belongs to the container. The volume calculator needs the contents, but should be able to get them from the container. So the changes would be something like this:
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks again. I'm hoping you can help clear up a couple points that have me confused.

I am trying to provide a better underlying structure for an existing interface. Let me try to provide the simplest example. Excuse the slight lack of alignment



In one panel the user chooses either a hard or soft container.
In another panel the user chooses type of content

The GUI is interactive so output reposts whenever a change is made and the program has sufficient information to calculate the output.

So let's say the user clicks on hard container and enters a volume- the output updates. If volume is changed, it outputs again. That is the simple case.

Now say the user clicks on soft container, output updates again. The program needs to look to see whether content 1 or content 2 is selected. If the appropriate field has a value, then the volume is calculated from that value and a value is put in the output field.

When the choice of content1 or content 2 is changed, the output field needs to change IF the soft container is selected.

I may have oversimplified, as there are a whole series of values calculated based just on the selection of container 1 or container 2. They all get recalculated when any change is made to the fields of the container and get used with the values calculated for the container to make the real output.

I keep going back and forth as to whether to put contents in container. I am using an observable that has a objects for hard container, soft container, content 1 and content 2 and enums for the current selection. So the content 1 panel, for example, would notify the observable to update its value of weight in content 1. The observable as part of that update changes any other value dependent on that value and then notifies the observers of a change. So the state of the observable is consistent when it sends out an update.

I was trying to reduce common code by having a container that had the same methods and signatures for the different containers and the same for the contents.

So I could have something more like:



rather than



If I put the content in the container, it seems like I still need to keep one outside, to keep the current state of the content in the interface and then update the one in the selected container whenever the selection changes.

I also have a question about the last bit of code you provided, but let me work on that myself a bit first.
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Alright, I think it is time to change directions a little bit. Before you start talking about the GUI, you should have the work (usually called the business logic) programmed and fully functional. Then you can add a GUI on top which uses the pre-developed business layer. Not only does it make it easier to manage later on, in my opinion it also makes it easier to program and test.

As a matter of fact, I have become a big fan of writing the tests first, then programming the business logic to make the tests pass, then programming the interface to work with the business layer. The 'make the tests first' approach ensures that you have the rules of what the code is supposed to do firmly identified before programming. It also helps flesh out the methods and objects that are required to do what you want. This is called 'Test Driver Programming' and is helped a lot by frameworks for 'Unit Testing' - such as JUnit.

I will give you an example - not using any framework - of a bare minimum tests based on your requirements for the HardContainer. Here is what I got so far:

requirements wrote:HardContainers should have a defined volume. There should be a way to set the volume. There should be a way to update other classes when the volume changes.

The HardContainer does not care about the type of contents when calculating volume.

The SoftContainer has different means of calculating volume.


So from this, I thought about design. First - there should be an interface for the containers. To be complete, the container interface should have volume, density, and contents. There should be a notification scheme, so I will add a custom listener. Because there is a different requirement for setting volumes depending on hard or soft containers, and the values required are known at execution time, we will use a 'visitor' to calculate the volume.

Conceptually I have this:
IContainer - interface for containers has two concrete implementations, HardContainer and SoftContainer
IVolumeCalculator - interface for setting a container's volume. Two concrete implementations, HardVolumeCalculator and SoftVolumeCalculator
IContainerChangeListener - interface to listen for changes
ContainerEvent - event used in the listener to describe the change that occured
IContent - interface for the contents. Not really sure what the spec is. Created a WeightedContent implementation as a place holder.

So now on to writing the test. I wrote this group of tests before writing any other code. This just (partially) covers the HardContainer


If you have a unit test framework, it gets a bit easier. But as you can see, I took a handful of conditions I expect the HardContainer to be able to handle, and tested them with basic values. To be complete I would use a larger range of tests (to define what should happen when invalid values are sent, for example). But if I run this code, I would get a bunch of errors and I would program the business logic until the errors go away. (the best route is actually to write one test, then write just the code required to make that test pass. Then the next test and the code to make it pass, etc...).

The reason I am showing you this is because it highlights a couple of things:
1) Program the business logic first
2) If you have a good set of tests, then it becomes relatively simple to see how a GUI can hook into things.
-- For example, in this case you should see that when your GUI would add an IContainerChangeListener to listen for when the volume changes (and use the notification to update the display),
-- and that when the GUI wants to set the volume for the HardContainer it should call container.setVolume(new HardVolumeCalculator(newVolume));.

I showed the tests for the simple case (HardContainer) but it won't be much different for the SoftContainer. The big differences would be:
1) When the content is changed on the SoftContainer, it should also cause the setVolume() method to be called (probably from within the SoftContainer's setContent() method)
2) When the setVolume() method is called, you would expect the argument to be a SoftVolumeCalculator (which uses the contents to calculate volume)
3) There would be no reason to call setVolume() on a SoftContainer by itself, it would usually just come from the setContent() call.
 
Jon Swanson
Ranch Hand
Posts: 230
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This discussion is really helping understand good programming practice.

This may be a stupid question, but does the following fall under business rules, or am I still focused too much on implementation?

There needs to be representation of a container.
The container may be any one of three types (currently, most likely there will be more, as originally there were only two types)
All containers have the same set of defined properties
How the properties are set depends on the type of container: for one type of container, a property is set based on either the values of the first or second type of content (below)- use second if defined, otherwise first.
All containers have a set of derived properties calculated in the same way (from the defined properties)
All containers must have a specific type of content, though it may be undefined when the container type is defined
The first type of content can come in two forms (currently)
This content has a set of derived properties calculated from the specified properties of the content
All containers may optionally have a second type of content
This also has derived properties
Some containers (depending on type) may have a third type of content
This also has derived properties

The container must return the derived properties of the first type of content
The container must return other properties derived from the derived properties of itself, and any of the content types that have been defined or return an indication that it does not have enough information to derive the properties

It should be possible change the type of container, presence of absence of any of the types of content, form of the first type of content or any specified values and have all derived values that can be updated, updated to reflect the change. The rest should be cleared.

There are variables common to all containers, which if changed, require all the derived values to be recomputed

 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!