Принцип разделения интерфейса - программа для интерфейса
Я читал о SOLID и других принципах дизайна. Я думал, что ISP - это то же самое, что "Программа для интерфейса, а не для реализации". Но, похоже, это разные принципы?
Есть ли разница?
6 ответов
ISP сфокусирован на идее каждого интерфейса, представляющего одно дискретное и связное поведение.
То есть каждая логическая группа вещей, которую должен делать объект, будет отображаться в одном конкретном интерфейсе. Класс может захотеть сделать несколько вещей, но каждая вещь будет привязана к определенному интерфейсу, представляющему это поведение. Идея в том, что каждый интерфейс очень сфокусирован.
Роберт Мартин очень хорошо объяснил принцип разделения интерфейса (ISP) в своей книге "UML для Java-программистов". Исходя из этого, я не думаю, что 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 знал больше, чем должен.
Предположим, что у вас есть один толстый интерфейс с множеством методов для реализации.
Любой класс, который реализует этот толстый интерфейс, должен обеспечивать реализацию для всех этих методов. Некоторые из методов могут быть неприменимы к этому конкретному классу. Но все же это должно обеспечить реализацию в отсутствие принципа разделения интерфейса.
Давайте посмотрим на пример кода в отсутствие сегрегации интерфейса.
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 childsRectangle : 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
класс, который придает 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
Интерфейс, вы используете принцип разделения интерфейса.
Это также хороший пример ситуации, когда абстрактный базовый класс не является хорошей идеей. Тема форума, профиль пользователя и новостная статья не являются даже концептуально отдаленными, поэтому попытка заставить их наследовать поведение при обсуждении приводит к странной связи с неродственным родителем. Используя определенный интерфейс, представляющий обсуждения, вы можете убедиться, что объекты, которые вы хотите обсуждать, совместимы с клиентским кодом, который будет управлять этими обсуждениями.
Этот пример также может быть хорошим кандидатом на использование Traits в PHP, чего бы это ни стоило.