我正在阅读有关 SOLID 和其他设计原则的信息。我认为 ISP 与“编程接口,而不是实现”相同。但看起来这些是不同的原则?
有区别吗?
我正在阅读有关 SOLID 和其他设计原则的信息。我认为 ISP 与“编程接口,而不是实现”相同。但看起来这些是不同的原则?
有区别吗?
Robert Martin 在他的“UML for Java Programmers”一书中对接口隔离原则(ISP)有很好的解释。基于此,我不认为 ISP 是关于“专注于”一个合乎逻辑的、连贯的一组事物的接口。因为,这是不言而喻的;或者,至少它应该不言而喻。每个类、接口或抽象类都应该这样设计。
那么,什么是ISP?让我用一个例子来解释它。假设你有一个类 A 和一个类 B,它是类 A 的客户端。假设类 A 有十个方法,其中 B 只使用了两个。那么,B 是否需要知道 A 的所有十个方法? 可能不是——信息隐藏原理。你暴露的越多,你创造的耦合机会就越多。出于这个原因,您可以在两个类之间插入一个接口,称为 C(隔离)。该接口只会声明 B 使用的两个方法,而 B 将依赖于该接口,而不是直接依赖于 A。
所以现在,
class A {
method1()
method2()
// more methods
method10()
}
class B {
A a = new A()
}
会变成
interface C {
method1()
method2()
}
class A implements C{
method1()
method2()
// more methods
method10()
}
class B {
C c = new A()
}
这可以防止 B 知道超出其应有的信息。
ISP 专注于每个接口代表一个离散和内聚行为的想法。
也就是说,一个对象应该做的每一个逻辑组将映射到一个特定的接口。一个类可能想做几件事,但每件事都会映射到代表该行为的特定接口。这个想法是每个界面都非常专注。
假设您有一个带有许多要实现的方法的胖接口。
任何实现该胖接口的类都必须为所有这些方法提供实现。某些方法可能不适用于该具体类。但它仍然必须在没有接口隔离原则的情况下提供实现。
让我们看一下没有 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());
}
}
输出:
java InterfaceNoSeggration
Rectangle area:200.0
Square area:225.0
Circle area:12.56
笔记:
Shape
是一个通用的胖接口,它包含所有Shape
实现所需的方法,如Rectangle
,Circle
和Square
. 但是在各个 Shape 子项中只需要一些方法
Rectangle : getLength(), getWidth(), getArea()
Square : getLength() and getArea()
Circle : getRadius() and getArea()
在没有隔离的情况下,所有 Shapes 都实现了整个胖接口:Shape.
如果我们将代码更改如下,我们可以通过接口隔离原理实现相同的输出。
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());
}
}
笔记:
现在个别形状像Rectangle
,Square
并且Circle
只实现了必需的接口并摆脱了未使用的方法。
同意楼上两个答案。只是举一个上面TrueWill代码味道的例子,你不应该发现自己这样做:
@Override
public void foo() {
//Not used: just needed to implement interface
}
IWorker 接口:
public interface IWorker {
public void work();
public void eat();
}
开发人员类:
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");
}
}
机器人类:
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");
}
}
如需更完整的示例,请访问此处。
这是此原理的真实示例(在 PHP 中)
问题陈述:
我希望各种形式的内容都有与之相关的评论/讨论。该内容可能是任何内容,从论坛主题到新闻文章,到用户的个人资料,再到对话式的私人消息。
建筑学
我们需要一个可重用的DiscussionManager
类,它将 a 附加Discussion
到给定的内容实体。但是,以上四个示例(以及更多示例)在概念上都是不同的。如果我们想DiscussionManager
使用它们,那么所有四个以上都需要有一个他们都共享的通用界面。没有其他方法DiscussionManager
可以使用它们,除非你想让你的论点赤裸裸(例如没有类型检查)。
解决方案:Discussable
与这些方法接口:
attachDiscussion($topic_id)
detachDiscussion()
getDiscussionID()
然后DiscussionManager
可能看起来像这样:
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) { ... }
}
这样,DiscussionManager
就不会关心它使用的各种内容类型的任何不相关的行为。它只关心它需要的行为,而不管这些行为与什么相关联。因此,通过为您想要讨论的每种内容类型提供一个Discussable
界面,您正在使用界面隔离原则。
这也是抽象基类不是一个好主意的情况的一个很好的例子。论坛主题、用户个人资料和新闻文章在概念上甚至根本不是一回事,因此试图让它们继承讨论行为会导致奇怪地耦合到不相关的父级。使用代表讨论的特定界面,您可以确保要进行讨论的实体与将管理这些讨论的客户端代码兼容。
这个例子也可能是在 PHP 中使用 Traits 的一个很好的候选,因为它是值得的。