oop - segregation - principios de diseño principio de inversion de dependencia
Principio de Segregación de Interfaz-Programa a una interfaz (6)
Estaba leyendo sobre SOLID y otros principios de diseño. Pensé que ISP era lo mismo que "Programa para una interfaz, no una implementación". Pero parece que estos son principios diferentes?
¿Hay una diferencia?
Interfaz IWorker:
public interface IWorker { public void work(); public void eat(); }
Clase de desarrollador:
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"); } }
Clase de robot:
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"); } }
Para un ejemplo más completo vaya here .
Aquí hay un ejemplo del mundo real de este principio (en PHP)
Planteamiento del problema:
Quiero que varias formas de contenido tengan comentarios / discusiones asociadas con ellos. Ese contenido puede ser cualquier cosa, desde un tema del foro, un artículo de noticias, el perfil de un usuario o un mensaje privado al estilo de conversación.
Arquitectura
Queremos una clase DiscussionManager
reutilizable que adjunte una Discussion
a una entidad de contenido determinada. Sin embargo, los cuatro ejemplos anteriores (y muchos más) son todos conceptualmente diferentes. Si queremos que el DiscussionManager
use, entonces los cuatro + necesitan tener una interfaz común que todos compartan. No hay otra forma en que DiscussionManager
use a menos que desee que sus argumentos queden desnudos (por ejemplo, sin verificación de tipos).
Solución: Interfaz Discussable
con estos métodos:
-
attachDiscussion($topic_id)
-
detachDiscussion()
-
getDiscussionID()
Entonces DiscussionManager
podría verse así:
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) { ... }
}
De esta manera, DiscussionManager
no se preocupa por ninguno de los comportamientos no relacionados de los diversos tipos de contenido que utiliza. SOLO se preocupa por los comportamientos que necesita, independientemente de con qué estén asociados esos comportamientos. Entonces, al proporcionar a cada tipo de contenido para el que desea tener discusiones, una interfaz de discusión, está utilizando el principio de segregación de la interfaz.
Este es también un buen ejemplo de una situación en la que una clase base abstracta no es una buena idea. Un tema del foro, un perfil de usuario y un artículo de noticias no son ni siquiera remotamente conceptuales lo mismo, por lo que intentar que hereden los comportamientos de discusión conduce a un extraño acoplamiento con un padre no relacionado. Al usar una interfaz específica que representa las discusiones, puede asegurarse de que las entidades que desea tener sean compatibles con el código del cliente que administrará esas discusiones.
Este ejemplo también podría ser un buen candidato para el uso de Rasgos en PHP, por lo que vale.
De acuerdo con las dos respuestas anteriores. Solo para dar un ejemplo del código de TrueWill que se menciona arriba, no deberías encontrarte haciendo esto:
@Override
public void foo() {
//Not used: just needed to implement interface
}
El ISP se centra en la idea de que cada interfaz representa un comportamiento discreto y cohesivo.
Es decir, cada grupo lógico de cosas que debería hacer un objeto se asignaría a una única interfaz específica. Una clase podría querer hacer varias cosas, pero cada cosa se asignaría a una interfaz específica que representa ese comportamiento. La idea es que cada interfaz está muy enfocada.
Robert Martin tiene una muy buena explicación del principio de segregación de interfaz (ISP), en su libro "UML para programadores de Java". Basándome en eso, no creo que ISP se trate de una interfaz que esté "enfocada" en un grupo lógico y coherente de cosas. Porque, eso no hace falta decirlo; O, al menos, debería ir sin decirlo. Cada clase, interfaz o clase abstracta debe ser diseñada de esa manera.
Entonces, ¿qué es ISP? Déjame explicarlo con un ejemplo. Supongamos que tiene una clase A y una clase B, que es el cliente de la clase A. Supongamos que la clase A tiene diez métodos, de los cuales B. solo usa dos. Ahora, B necesita saber acerca de los diez métodos de A ? Probablemente no - el principio de la información oculta. Cuanto más exponga, más creará la posibilidad de acoplamiento. Por ese motivo, puede insertar una interfaz, llámela C, entre las dos clases (segregación). Esa interfaz solo declarará los dos métodos que usa B, y B dependerá de esa interfaz, en lugar de hacerlo directamente de A.
Y ahora,
class A {
method1()
method2()
// more methods
method10()
}
class B {
A a = new A()
}
se convertirá
interface C {
method1()
method2()
}
class A implements C{
method1()
method2()
// more methods
method10()
}
class B {
C c = new A()
}
Esto, evita que B sepa más de lo que debería.
Supongamos que tiene una interfaz grande con muchos métodos para implementar.
Cualquier clase, que implemente esa interfaz fat, tiene que proporcionar implementación para todos estos métodos. Algunos de los métodos pueden no ser aplicables a esa clase concreta. Pero todavía tiene que proporcionar implementación en ausencia del principio de segregación de interfaz.
Veamos el código de ejemplo en ausencia de la segregación de interfaz .
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());
}
}
salida:
java InterfaceNoSeggration
Rectangle area:200.0
Square area:225.0
Circle area:12.56
Notas:
Shape
es una interfaz fat de propósito general, que contiene métodos requeridos para todas las implementaciones deShape
comoRectangle
,Circle
ySquare
. Pero solo se necesitan algunos métodos en los respectivos niños de Shape.Rectangle : getLength(), getWidth(), getArea() Square : getLength() and getArea() Circle : getRadius() and getArea()
En ausencia de segregación, todas las Formas han implementado una interfaz completa de grasa: Forma.
Podemos lograr la misma salida con el principio de segregación de interfaz si cambiamos el código de la siguiente manera.
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());
}
}
Notas:
Ahora, las formas individuales como Rectangle
, Square
y Circle
han implementado solo las interfaces requeridas y se han deshecho de los métodos no utilizados.