问题
I was reading about SOLID and other design principles. I thought ISP was the same as "Program to an interface, not an implementation". But it looks like these are different principles?
Is there a difference?
回答1:
ISP is focused on the idea of each interface representing one discrete and cohesive behavior.
That is, each logical group of things an object should do would map to a single specific interface. A class might want to do several things, but each thing would map to a specific interface representing that behavior. The idea is each interface is very focused.
回答2:
Robert Martin has a very good explanation of Interface segregation principle (ISP), in his book "UML for Java Programmers". Based on that, I don't think ISP is about an interface being "focused" on one logical, coherent group of things. Because, that goes without saying; or, at least it should go without saying. Each class, interface or abstract class should be designed that way.
So, what is ISP? Let me explain it with an example. Say, you have a class A and a class B, which is the client of class A. Suppose, class A has ten methods, of which only two are used by B. Now, does B need to know about all ten methods of A? Probably not - the principle of Information hiding. The more you expose, the more you create the chance for coupling. For that reason, you may insert an interface, call it C, between the two classes (segregation). That interface will only declare the two methods that are used by B, and B will depend on that Interface, instead of directly on A.
So now,
class A {
method1()
method2()
// more methods
method10()
}
class B {
A a = new A()
}
will become
interface C {
method1()
method2()
}
class A implements C{
method1()
method2()
// more methods
method10()
}
class B {
C c = new A()
}
This, prevents B from knowing more than it should.
回答3:
Assume that you have one fat interface with many methods to be implemented.
Any class, that implements that fat interface has to provide implementation for all these methods. Some of the methods may not be applicable to that concrete class. But still it has to provide implementation in absence of interface segregation principle.
Let's have a look at example code in absence of Interface segregation.
interface Shape{
public int getLength();
public int getWidth();
public int getRadius();
public double getArea();
}
class Rectangle implements Shape{
int length;
int width;
public Rectangle(int length, int width){
this.length = length;
this.width = width;
}
public int getLength(){
return length;
}
public int getWidth(){
return width;
}
public int getRadius(){
// Not applicable
return 0;
}
public double getArea(){
return width * length;
}
}
class Square implements Shape{
int length;
public Square(int length){
this.length = length;
}
public int getLength(){
return length;
}
public int getWidth(){
// Not applicable
return 0;
}
public int getRadius(){
// Not applicable
return 0;
}
public double getArea(){
return length * length;
}
}
class Circle implements Shape{
int radius;
public Circle(int radius){
this.radius = radius;
}
public int getLength(){
// Not applicable
return 0;
}
public int getWidth(){
// Not applicable
return 0;
}
public int getRadius(){
return radius;
}
public double getArea(){
return 3.14* radius * radius;
}
}
public class InterfaceNoSeggration{
public static void main(String args[]){
Rectangle r = new Rectangle(10,20);
Square s = new Square(15);
Circle c = new Circle(2);
System.out.println("Rectangle area:"+r.getArea());
System.out.println("Square area:"+s.getArea());
System.out.println("Circle area:"+c.getArea());
}
}
output:
java InterfaceNoSeggration
Rectangle area:200.0
Square area:225.0
Circle area:12.56
Notes:
Shape
is a general purpose fat interface, which contains methods required for allShape
implementations likeRectangle
,Circle
andSquare
. But only some methods are needed in respective Shape childsRectangle : getLength(), getWidth(), getArea() Square : getLength() and getArea() Circle : getRadius() and getArea()
In absence of segregation, all Shapes have implemented entire fat interface : Shape.
We can achieve same output with interface segregation principle if we change the code as follows.
interface Length{
public int getLength();
}
interface Width{
public int getWidth();
}
interface Radius{
public int getRadius();
}
interface Area {
public double getArea();
}
class Rectangle implements Length,Width,Area{
int length;
int width;
public Rectangle(int length, int width){
this.length = length;
this.width = width;
}
public int getLength(){
return length;
}
public int getWidth(){
return width;
}
public int getRadius(){
// Not applicable
return 0;
}
public double getArea(){
return width * length;
}
}
class Square implements Length,Area{
int length;
public Square(int length){
this.length = length;
}
public int getLength(){
return length;
}
public int getWidth(){
// Not applicable
return 0;
}
public int getRadius(){
// Not applicable
return 0;
}
public double getArea(){
return length * length;
}
}
class Circle implements Radius,Area{
int radius;
public Circle(int radius){
this.radius = radius;
}
public int getLength(){
// Not applicable
return 0;
}
public int getWidth(){
// Not applicable
return 0;
}
public int getRadius(){
return radius;
}
public double getArea(){
return 3.14* radius * radius;
}
}
public class InterfaceSeggration{
public static void main(String args[]){
Rectangle r = new Rectangle(10,20);
Square s = new Square(15);
Circle c = new Circle(2);
System.out.println("Rectangle area:"+r.getArea());
System.out.println("Square area:"+s.getArea());
System.out.println("Circle area:"+c.getArea());
}
}
Notes:
Now individual Shapes like Rectangle
, Square
and Circle
have implemented only required interfaces and got rid of un-used methods.
回答4:
Agree with both the answers above. Just to give an example of TrueWill's code smell above, you shouldn't find yourself doing this:
@Override
public void foo() {
//Not used: just needed to implement interface
}
回答5:
IWorker Interface:
public interface IWorker { public void work(); public void eat(); }
Developer Class :
public class Developer implements IWorker { @Override public void work() { // TODO Auto-generated method stub System.out.println("Developer working"); } @Override public void eat() { // TODO Auto-generated method stub System.out.println("developer eating"); } }
Robot Class:
public class Robot implements IWorker { @Override public void work() { // TODO Auto-generated method stub System.out.println("robot is working"); } @Override public void eat() { // TODO Auto-generated method stub throw new UnsupportedOperationException("cannot eat"); } }
For a more complete example go here.
回答6:
Here's a real-world example of this principle (in PHP)
Problem Statement:
I want various forms of content to have comments/discussion associated with them. That content might be anything from a forum topic, to a news article, to a user's profile, to a conversation-style private message.
Architecture
We will want a re-usable DiscussionManager
class which attaches a Discussion
to a given content entity. However, the above four examples (and many more) are all conceptually different. If we want the DiscussionManager
to use them, then all four+ need to have one common interface that they all share. There is no other way for DiscussionManager
to use them unless you want to your arguments to go naked (e.g. no type checking).
Solution: Discussable
interface with these methods:
attachDiscussion($topic_id)
detachDiscussion()
getDiscussionID()
Then DiscussionManager
might look like this:
class DiscussionManager
{
public function addDiscussionToContent(Discussable $Content)
{
$Discussion = $this->DiscussionFactory->make( ...some data...);
$Discussion->save() // Or $this->DiscussionRepository->save($Discussion);
$Content->attachDiscussion($Discussion->getID()); // Maybe saves itself, or you can save through a repository
}
public function deleteDiscussion(Discussable $Content)
{
$id = $Content->getDiscussionID();
$Content->detatchDiscussion();
$this->DiscussionRepository->delete($id);
}
public function closeDiscussion($discussion_id) { ... }
}
This way, DiscussionManager
does not care about any of the unrelated behaviors of the various content types that it uses. It ONLY cares about the behaviors it needs, regardless of what those behaviors are associated with. So by giving each content type that you want to have discussions for, a Discussable
interface, you are using the interface segregation principle.
This is also a good example of a situation where an abstract base class is not a good idea. A forum topic, user profile, and news article aren't even remotely conceptually the same thing, thus trying to get them to inherit the discussion behaviors leads to strange coupling to an unrelated parent. Using a specific interface that represents discussions, you can makes sure that the entities you want to have discussions, are compatible with the client code that will be managing those discussions.
This example might also be a good candidate for usage of Traits in PHP, for what it's worth.
来源:https://stackoverflow.com/questions/9249832/interface-segregation-principle-program-to-an-interface