As you know the 'program to an interface' des开发者_开发知识库ign principle broadly prefers supertypes instead of concrete types or implementations.
Is it consistent with the principle to use instanceof in a Java program to derive a concrete type from a supertype?
In my application, Storehouse is an abstract supertype class with a couple of private variables and public getters and setters.
ConcreteStorehouseA inherits from Storehouse and has a lot of concrete methods and variables. ConcreteStorehouseB is similar but different.
My application receives a Storehouse. However, Storehouse is not a useful type to operate on. Because the only really useful methods are contained in the concrete types, I use instanceof as follows:
if (storehouse instanceof ConcreteStorehouseA) {
ConcreteStorehouseA concreteStorehouseA = (ConcreteStorehouseA) storehouse;
// perform operations on the concrete type's useful methods and variables
Is using instanceof compatible with the principle?
Edit:
In essence the application is a dice simulator for a table top RPG, Shadowrun. The concrete types are the different test types - Success Test, Opposed Test, Extended Test - which all have very different factors and parameters for their successful operation. The supertype essentially contains the dice pool!
As a rule of thumb, that "program to interfaces" principle that you mentioned can be translated into: import
only the interface type, don't have any compile-time dependency on subclasses.
Therefore, the answer to your question would be definitely no. You are not programming to interfaces since you cast to concrete types.
You've said it yourself:
My application receives a Storehouse. However, Storehouse is not a useful type to operate on. Because the only really useful methods are contained in the concrete types
In other words, your Storehouse
abstraction isn't buying you anything... why do you have it?
Could you create abstract methods in Storehouse
, implemented in each concrete class, which would then let you treat the concrete types the same way in your client code? That's the goal of the abstraction.
Not really. If your method has wildly different behaviour depending on the type that it receives, then polymorphism is buying you nothing. You should consider two separate overloaded methods, one taking a ConcreteStorehouseA
as an argument, and the other taking a ConcreteStorehouseB
.
It is not always a sin. Suppose you are implementing a spec, which says, if x is A do this, else if x is Y do that. It's better to have your code look like the spec. To artificially cut it into little pieces and places them into difference sources, is not only pretentious, but also complicated, unsafe, hard to understand and hard to maintain.
Programming concerns are multi-dimensional. Programming languages are one dimensional, at least for now. It is an art how to organize related concerns closely. Do not buy into the dogma that concerns must be decomposed according to classes, then a concern can at most reference one class, and it must reside inside that class.
Last century there was this great taboo in software engineering, according to the experts who knew how to write better softwares: that is, code change should be avoided at all cost. If you are going to change some source code you wrote previously, the universe could collapse into a peanut at any moment. So you better design a perfect architecture from the start, and then any change in requirement can be done by adding a new/clean class, without touching existing code base.
That was, let me be very precise in wording here, total nonsense, even back then.
Today, we have much better tools, and code change is not only safe, it's even encouraged. The best way to make sure code change is easy tomorrow due to unforeseeable reasons, is to keep code today as simple as possible. Write code in a way that you can understand even in a comma.
It can be difficult to be sure without insight into what operations you are performing, but a more elegant design would be to have StoreHouse define method signatures for the different "operations". Then, instead of the if(instanceof) checks, you just execute the operations, and the concreteStorehouses would implement those operations.
public abstract class Storehouse
//add these definitions;
public void operation1();
public void operation2();
public class ConcreteStorehouseA
public void operation1() {
//do operation
}
public void operation2() {
//do operation
}
public class ConcreteStorehouseB
public void operation1() {
//do operation
}
public void operation2() {
//do operation
}
In your calling code, instead of:
if (storehouse instanceof ConcreteStorehouseA) {
ConcreteStorehouseA concreteStorehouseA = (ConcreteStorehouseA) storehouse;
// perform operations
} else if (storehouse instanceof ConcreteStorehouseB) {
ConcreteStorehouseB concreteStorehouseB = (ConcreteStorehouseB) storehouse;
// perform operations
}
You can then use polymorphism to do the operations without caring which implementation is being executed.
storehouse.operation1();
storehouse.operation2();
where concreteStorehouseA and ConcreteStorehouseB have defined implementations for those operations.
It sounds like the Storehouse
abstraction isn't really a proper abstraction at all in this case. As a type by itself it isn't really providing you with anything.
One thing that may help is to try not to think of your implementations as "types" at all. The "type" is the abstraction that the class is implementing. (Basically, pull apart the terms "class" and "type" in your thinking.) So the type you're working with is a Storehouse
. ConcreteStorehouseA
is a Storehouse
, and so is ConcreteStorehouseB
. So the question becomes, what is a Storehouse
? That's the type that defines what each one is.
In this particular case it sounds like the different Storehouse
implementations are so different (at least in their current implementation) that they don't really share a worth-while abstraction. The abstract "type" in this case is just providing some common functionality by means of inheritance rather than actually abstracting the type.
To put it another way, it sounds like the Storehouse
implementations here are a classic example of the Liskov Substitution motivational poster here:
http://www.lostechies.com/blogs/derickbailey/archive/2009/02/11/solid-development-principles-in-motivational-pictures.aspx
As various people have pointed out, this is not programming to the interface. Apparently, the interface abstraction is too weak for your requirements.
There are various ways to deal with this, among which:
- If the interface is under your control, you can expand it. Consider whether that's worth it, it may be too high a price to pay, especially if the next bullet point holds.
- If you end up using only one kind of concrete warehouse, you can just use that directly.
- If you need to support various kinds of warehouses, a good alternative is to define your own interface that provides exactly the abstraction you need it and write a thin wrapper for each kind of concrete class (adapter pattern).
精彩评论