Multiple inheritance design issue in Java
How do you deal with having only single inheritance in java? Here is my specific problem:
I have three (simplified) classes:
public abstract class AbstractWord{
String kind; // eg noun, verb, etc
public String getKind(){ return kind; }
}
public class Word extends AbstractWord{
public final String word;
ctor...
public void setKind(){
// based on the variable word calculate kind..
}
}
public class WordDescriptor extends AbstractWord{
ctor..
public void setKind(String kind){this.kind = kind;}
}
This is what I consider my most basic implementation but I want to make other implementations.
Lets say that I want to add a new variable say wordLength but I want to add it using inheritance. Meaning I do NOT want modify that original AbstractWord class. Ie something along the lines of this:
public class Length{
private int length;
public int getLength(){return length};
}
public class BetterWord extends AbstractWord AND Length{
public void setLength(){
// based on the variable word calculate Length..
}
}
public class BetterWordDescriptor extends AbstractWord AND length{
public void setLength(int length){this.length = length;}
}
I know that java does not let me do this but it has made my code very开发者_如何转开发 ugly. Right now whenever I add a field I am just adding it to AbstractWord but then I either need to rename that AbstractWord (and Word and WordDescriptor). (I can't just add the field to the other one because of backwards compatibility, it break equals methods and stuff like that).
This seems like a pretty common design issue but I have racked my head and I cannot come up with any beautiful solutions.
Is there a design pattern that addresses this? I have some potential solutions but I wanted to see if there was something that I was missing.
thanks, Jake
Update: Length refers to the number of syllables in the word (sorry about the lack of clarity)
Favor composition over inheritance.
Solution takes into consideration that there could be another type of word that may need WordLengthSupport.
Similarly other interfaces could be created and implemented and various word types can have mix and match of those interfaces.
.
public class WordLength {
private int length = 0;
public int getLength(){return length};
public void setLength(int length){this.length = length};
}
.
public interface WordLengthSupport {
public WordLength getWordLength();
}
.
public class BetterWord extends AbstractWord
implements WordLengthSupport {
WordLength wordLength;
public WordLength getWordLength() {
if(wordLength==null) {
// each time word changes
// make sure to set wordLength to null
calculateWordLength();
}
return wordLength;
}
private void calculateWordLength() {
// This method should be
// called in constructor
// or each time word changes
int length = // based on the variable word calculate Length..
this.wordLength = new WordLength();
this.wordLength.setLength(length);
}
}
.
public class BetterWordDescriptor extends AbstractWord
implements WordLengthSupport {
WordLength wordLength;
public WordLength getWordLength(return wordLength);
public void setWordLength(WordLength wordLength) {
// Use this to populate WordLength of respective word
this.wordLength = wordLength;
}
}
.
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
This solution does not use strategy pattern but can be refactored for same.
Just use composition instead of inheritance:
a BetterWord
is-an AbstractWord
that has-a Length
:
public class BetterWord extends AbstractWord {
private Length length;
public void setLength(int value){
length.setLength(value);
}
}
EDIT
If the API needs an object of type Length, just add a getter:
public class BetterWord extends AbstractWord {
private Length length;
public void setLength(int value){
length.setLength(value);
}
public Length getLength() {
return length
}
}
Or rename the implementation Length
to LengthImpl
and define an interface Length
, because a class can implement multiple interfaces.
With your specific example you could use the decorator pattern in conjunction with interfaces to supplement your Word
class with additional functionality; e.g.
// *Optional* interface, useful if we wish to reference Words along with
// other classes that support the concept of "length".
public interface Length {
int getLength();
}
// Decorator class that wraps another Word and provides additional
// functionality. Could add any additional fields here too.
public class WordExt extends AbstractWord implements Length {
private final Word word;
public class(Word word) {
this.word = word;
}
public int getLength() {
return word.getKind().length();
}
}
In addition it's worth noting that the lack of multiple inheritence in Java isn't really the issue here; it's more a case of reworking your design. In general it's considered bad practice to over-use inheritence as deep inheritence hierarchies are difficult to interpret / maintain.
Looking at it, my first feeling is that your model is a bit complicated.
A word has a String to describe the word itself being stored in the Word object along with a class to say it's a noun, verb, adjective, etc. Another property of a Word is the length of the string stored in the Word object.
Think of things in terms of "is-a" and "has-a" relationships and you can remove a lot of complexity.
For instance why do you need a WordDescriptor that extends AbstractWord? Is a word going to change from a verb to an adjective? I would have thought that the word type was set when the object was created and would not change during the lifetime of the Word object. Once you had a Word object for the word "Australia" the Kind of word would not change for the lifetime of the object.
Hmmm. Maybe you might have a Word object representing the word "bark" after instantiating the object with a type of "verb" to describe the sound a dog makes. Then you realise that you actually needed to have the Word object to represent a noun that describes the covering of a tree. Possible, but both the dog's bark and the tree's bark can exist.
So I think the model you've chosen is a bit too complicated and that your question can be resolved by going back and simplifying your original object model.
Start by asking yourself a question for each of the inheritance aspects of your basic model.
When I say Class B extends Class A, can I say that Class B "is-a" Class A and that I am specialising its behaviour?
For example, a base class Animal can be extended to provide the specialised class of Kangaroo. Then you can say that "the kangaroo "is-a" Animal. You are specialising the behaviour.
Then look at the attributes, a Kangaroo has a Location attribute to describe where it is found. Then you can say a Kangaroo "has-a" location. A Kangaroo "is-a" location doesn't make sense.
Similarly, a Word "has-a" length. And the statement a Word "is-a" length just doesn't make sense.
BTW All Australian references in this post are deliberate to celebrate Australia Day which is today 26th January!
HTH
(I can't just add the field to the other one because of backwards compatibility, it break equals methods and stuff like that).
It won't break source compatibility. Not unless you're doing something really crazy in your equals methods.
And renaming your classes is generally not the way to handle binary compatibility.
The problem is not "how to deal with single inheritance". What you're missing is not really a design pattern but learning to design the API separately from the implementation.
I would implement it like so:
public interface WordDescriptor {
void getKind();
Word getWord();
}
public interface Word {
String getWord();
}
public class SimpleWord implements Word {
private String word;
public SimpleWord(String word) { this.word = word; }
public String getWord() { return word; }
}
public class SimpleWordDescriptor implements WordDescriptor {
private Word word;
private String kind;
public SimpleWordDescriptor(Word word, String kind) {
this.word = word;
this.kind = kind; // even better if WordDescriptor can figure it out internally
}
public Word getWord() { return word; }
public String getKind() { return kind; }
}
With this basic setup, when you want to introduce a length property, all you have to do is this:
public interface LengthDescriptor {
int getLength();
}
public class BetterWordDescriptor extends SimpleWordDescriptor
implements LengthDescriptor {
public BetterWordDescriptor(Word word, String kind) {
super(word, kind);
}
public int getLength() { getWord().length(); }
}
The other answers that uses composition of properties as well as the Decorator pattern are also entirely valid solutions to your problem. You just need to identify what your objects are and how "composable" they are, and how they are to be used - hence designing the API first.
/** * First example */
class FieldsOfClassA {
public int field1;
public char field2;
}
interface IClassA {
public FieldsOfClassA getFieldsA();
}
class CClassA implements IClassA {
private FieldsOfClassA fields;
@Override
public FieldsOfClassA getFieldsA() {
return fields;
}
}
/**
* seems ok for now
* but let's inherit this sht
*/
class FieldsOfClassB {
public int field3;
public char field4;
}
interface IClassB extends IClassA {
public FieldsOfClassA getFieldsA();
public FieldsOfClassB getFieldsB();
}
class CClassB implements IClassB {
private FieldsOfClassA fieldsA;
private FieldsOfClassB fieldsB;
@Override
public FieldsOfClassA getFieldsA() {
return fieldsA;
}
@Override
public FieldsOfClassB getFieldsB() {
return fieldsB;
}
}
/**
wow this monster got bigger
imagine that you will need 4 lvl of inheritance
it would take so much time to write this hell
I'm even not talking that user of those iface will think
what fields i will need fieldsA fieldsB fieldsC or another one
So composition does not work here and your pathetic tries are useless
When u think about Oject Oriented programming
u need BIG models with 6-7 lvls of multiple inheritance
because that is good test and because corresponds to models of real life or math models tested by civilization for 4 thousands years.
If your models require 2 lvl of inheritance stop pretending u using OO
U can easily implement it with any language even procedural one like C or Basic language */
精彩评论