Patrones de diseño - Introducción a los patrones de diseño y siete principios

 navegación: 

[Dark Horse Java Notes + Stepping on the Pit Summary] JavaSE+JavaWeb+SSM+SpringBoot+Riji Takeaway+SpringCloud+Dark Horse Tourism+Guli Mall+Xuecheng Online+Design Mode+Nioke Interview Questions

Tabla de contenido

1. Comprenda los puntos clave a través de preguntas de entrevista clásicas

2. El propósito y los principios básicos del patrón de diseño.

3. Siete principios del patrón de diseño.

3.1 Principio de responsabilidad única

3.2 Principio de segregación de interfaz (Principio de segregación de interfaz)

3.3 Principio de Inversión de Dependencia (Principio de Inversión de Dependencia)

3.3.1 Introducción

3.3.2 Tres formas de transferencia de dependencia

3.4 Principio de sustitución de Liskov

3.5 Principio abierto cerrado

3.6 Principio de Deméter

3.7 Principio de reutilización de compuestos

4. Diagrama de clases UML: lenguaje de modelado unificado

4.1, diagrama de clases - dependencia (dependencia)

4.2, diagrama de clases - generalización (Generalización)

4.3, diagrama de clases - implementación (Implementación)

4.4, diagrama de clases - asociación (Association)

4.5, diagrama de clases - agregación (Agregación)

4.6, diagrama de clases - composición (Composición)


1. Comprenda los puntos clave a través de preguntas de entrevista clásicas

patrón de creación de prototipos 

  • 1) Utilice diagramas de clase UML para dibujar la función central del modo prototipo
  • 2) ¿Cuáles son la copia profunda y la copia superficial del modo de diseño de prototipo , y escriba el código fuente de los dos métodos de copia profunda (reescriba el método de clonación para lograr una copia profunda, use la serialización para lograr una copia profunda)
  • 3) Dónde usar el modo prototipo en Spring Framework y analizar el código fuente
<bean id="id01"class="com.atguigu.spring.bean.Monster" scope="prototype"/>
  • 4) La creación de beans prototipo en Spring es la aplicación del modo prototipo.
  • 5) Análisis de código + código fuente de depuración

Siete principios de patrones de diseño

  • 1) Principio de responsabilidad única
  • 2) Principio de aislamiento de interfaz
  • 3) Principio de inversión de dependencia
  • 4) Principio de sustitución de Liskov
  • 5) Principio Abierto Cerrado (OCP)
  • 6) Ley de Deméter
  • 7) El principio de reutilización sintética

Requerir:

  • 1) La idea central de los siete principios de diseño.
  • 2) Ser capaz de ilustrar los principios de diseño con diagramas de clase.
  • 3) En el desarrollo real del proyecto, ¿dónde utilizó el principio OCP?

modo de estado

Proyecto de plataforma de préstamos financieros: el pedido de la plataforma de préstamos tiene pasos como revisión, liberación, captura de pedidos, etc. El estado del pedido cambiará con diferentes operaciones. La implementación de este módulo en el proyecto utilizará el modelo de estado, por favor use los patrones de estado y complete el código real

Análisis del problema: este tipo de código es difícil de hacer frente a los cambios. Al agregar un estado, debemos agregar manualmente if/else. Al agregar una función, debemos juzgar todos los estados. Por lo tanto, el código se hinchará cada vez más y, una vez que no se procese un determinado estado, se producirán errores extremadamente graves, que son difíciles de mantener.

Patrón de diseño de intérprete

  • 1) Introduce ¿qué es el patrón de diseño del intérprete?
  • 2) Dibuje el diagrama de clases UML del patrón de diseño del intérprete y analice cuáles son los roles en el patrón de diseño.
  • 3) Explique dónde se usa el patrón de diseño del intérprete en el marco Spring y realice un análisis de nivel de código fuente.

patrón de diseño único

¿Cuántas implementaciones hay en el patrón de diseño singleton? Utilice el código para lograrlo y explique las ventajas y los puntos reales de cada implementación.

  • 1) dos tipos de estilo chino hambriento
  • 2) tres tipos de estilo perezoso
  • 3) Doble verificación
  • 4) Clase interna estática
  • 5) enumeración

2. El propósito y los principios básicos del patrón de diseño.

importancia: 

  1. Los patrones de diseño son soluciones a problemas omnipresentes en el diseño de software.
  2. Es conveniente mantener el proyecto (legibilidad, estandarización) y agregar nuevas funciones una vez finalizado el desarrollo del proyecto.
  3. ¿Qué patrones de diseño ha utilizado en proyectos reales y cómo? ¿Qué problema resolvió?
  4. Los patrones de diseño se utilizan en los módulos funcionales y el marco del proyecto.

Propósito del patrón de diseño:

En el proceso de escribir software, los programadores enfrentan desafíos de acoplamiento, cohesión, mantenibilidad, escalabilidad, reutilización, flexibilidad, etc.

Los patrones de diseño están diseñados para permitir que los programas (software) tengan mejores

  • 1) Reutilización (es decir, el código con la misma función no necesita escribirse varias veces, también llamado reutilización de código)
  • 2) Legibilidad (es decir: programación normativa, fácil de leer y comprender para otros programadores)
  • 3) Escalabilidad (es decir, cuando es necesario agregar nuevas funciones, es muy conveniente, también llamado mantenibilidad)
  • 4) Confiabilidad (es decir: cuando agregamos nuevas funciones, no afectará las funciones originales)
  • 5) Hacer que el programa presente las características de alta cohesión y bajo acoplamiento

La idea central de los principios de diseño.

  • 1) Averigüe qué puede ser necesario cambiar en la aplicación , sepárelos y no los mezcle con código que no necesita ser cambiado
  • 2) Programa a la interfaz , no a la implementación.
  • 3) Esfuércese por un diseño débilmente acoplado entre objetos interactivos

El patrón de diseño contiene la esencia del diseño orientado a objetos, "Si comprende el patrón de diseño, comprenderá la esencia del análisis y diseño orientado a objetos (OOA/D)"

3. Siete principios del patrón de diseño.

Los siete principios que se utilizan comúnmente son:

  • 1) Principio de responsabilidad única
  • 2) Principio de aislamiento de interfaz
  • 3) Principio de inversión de dependencia
  • 4) Principio de sustitución de Liskov
  • 5) El principio de apertura y cierre
  • 6) Ley de Deméter
  • 7) El principio de reutilización sintética

3.1 Principio de responsabilidad única

Para las clases, es decir, una clase solo debe ser responsable de una responsabilidad .

Por ejemplo, la tabla de usuario solo es responsable de almacenar información relacionada con el usuario. Por ejemplo, la Clase A es responsable de dos responsabilidades diferentes: Responsabilidad 1 y Responsabilidad 2. Cuando los requisitos de la responsabilidad 1 cambian y A cambia, puede causar errores de ejecución de la responsabilidad 2, por lo que la granularidad de la clase A debe descomponerse en A1, A2

Notas y detalles

  • 1) Reducir la complejidad de la clase, una clase solo es responsable de una responsabilidad
  • 2) Mejorar la legibilidad y mantenibilidad de la clase
  • 3) Reducir el riesgo causado por los cambios
  • 4) En circunstancias normales, debemos acatar el principio de responsabilidad única. Solo cuando la lógica es lo suficientemente simple se puede violar el principio de responsabilidad única a nivel de código; solo cuando el número de métodos en la clase es lo suficientemente pequeño , el el principio de responsabilidad única se mantenga a nivel de método

caso:

Escenario 1 (violación del principio único), juicio condicional dentro del método, distinguiendo escenarios:

public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车");
        vehicle.run("轮船");
        vehicle.run("飞机");
    }
}

/**
 * 方式1的分析
 * 1.在方式1的run方法中,违反了单一职责原则
 * 2.解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
 */
class Vehicle{
    public void run(String type){
        if ("汽车".equals(type)) {
            System.out.println(type + "在公路上运行...");
        } else if ("轮船".equals(type)) {
            System.out.println(type + "在水面上运行...");
        } else if ("飞机".equals(type)) {
            System.out.println(type + "在天空上运行...");
        }
    }
}

Escenario 2 (responsabilidad única): Diferentes clases, diferentes escenarios:

public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("汽车");
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");
    }
}

/**
 * 方案2的分析
 * 1.遵守单一职责原则
 * 2.但是这样做的改动很大,即将类分解,同时修改客户端
 * 3.改进:直接修改Vehicle类,改动的代码会比较少=>方案3
 */
class RoadVehicle{
    public void run(String type){
        System.out.println(type + "在公路上运行...");
    }
}
class WaterVehicle{
    public void run(String type){
        System.out.println(type + "在水面上运行...");
    }
}
class AirVehicle{
    public void run(String type){
        System.out.println(type + "在天空上运行...");
    }
}

Escenario 3 (responsabilidad única a nivel de método): diferentes métodos, diferentes escenarios:

public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle2 vehicle = new Vehicle2();
        vehicle.run("汽车");
        vehicle.runWater("轮船");
        vehicle.runAir("飞机");
    }
}

/**
 * 方式3的分析
 * 1.这种修改方法没有对原来的类做大的修改,只是增加方法
 * 2.这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
 */
class Vehicle2{
    public void run(String type){
        System.out.println(type + "在公路上运行...");
    }
    public void runWater(String type){
        System.out.println(type + "在水面上运行...");
    }
    public void runAir(String type){
        System.out.println(type + "在天空上运行...");
    }
}

3.2 Principio de segregación de interfaz (Principio de segregación de interfaz)

El cliente no debe depender de interfaces que no necesita , es decir, la dependencia de una clase con otra clase debe establecerse en la interfaz más pequeña

El Principio de segregación de la interfaz (Principio de segregación de la interfaz, ISP) es un principio de diseño en SOLID, que se define como "no se debe obligar al cliente a depender de métodos que no usa", es decir, no se debe obligar a una clase a confiar en interfaces que no necesita.

El objetivo principal del principio de segregación de interfaces es dividir las interfaces grandes e infladas en interfaces más pequeñas y específicas , para que los clientes puedan elegir la interfaz específica que necesitan según sus necesidades. Esto puede reducir en gran medida la dependencia del cliente de interfaces innecesarias, lo que hace que el sistema sea más flexible, mantenible y fácil de expandir.

Un ejemplo típico es que cuando necesitamos usar una interfaz, generalmente necesitamos implementar todos los métodos de la interfaz , pero de hecho solo se pueden usar algunos métodos . Si esta interfaz contiene muchos métodos, provocará redundancia de código y una dependencia excesiva de la clase de implementación.

A través de una división y combinación razonables de la interfaz , la interfaz se puede simplificar y se puede mejorar la reutilización y la escalabilidad del código . Al mismo tiempo, también es propicio para mejorar la mantenibilidad del código y reducir el riesgo y el costo de mantenimiento de la modificación del código.

Cabe señalar que una interfaz es una abstracción que describe el comportamiento, y el propósito del aislamiento es hacer que la interfaz describa mejor el comportamiento abstracto, en lugar de hacer que la cantidad de interfaces sea más complicada. Por lo tanto, debemos ser moderados en el aislamiento de la interfaz y seleccionar y dividir según la situación específica.

Casos que no cumplen con el principio de segregación de interfaces:

  • La clase A depende de la clase B a través de la interfaz Interfaz1, y la clase C depende de la clase D a través de la interfaz Interfaz1. debe implementarse), luego la clase B y la clase D deben implementar los métodos que no necesitan
  • De acuerdo con el principio de aislamiento, debe manejarse de la siguiente manera: la interfaz Interface1 se divide en varias interfaces independientes, y la clase A y la clase C respectivamente establecen dependencias con las interfaces que necesitan. Es decir, el principio de aislamiento de interfaz.

Código segregado ilegalmente: 

interface Interface1 {
    void operation1();

    void operation2();

    void operation3();

    void operation4();

    void operation5();
}

class B implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3");
    }

    @Override
    public void operation4() {
        System.out.println("B 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("B 实现了 operation5");
    }
}

class D implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("D 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("D 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("D 实现了 operation3");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 operation5");
    }
}

/**
 * A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法
 */
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend2(Interface1 i) {
        i.operation2();
    }

    public void depend3(Interface1 i) {
        i.operation3();
    }
}

/**
 * C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
 */
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface1 i) {
        i.operation4();
    }

    public void depend5(Interface1 i) {
        i.operation5();
    }
}

Código después de dividir la interfaz:

interface Interface1 {
    void operation1();
}

interface Interface2 {
    void operation2();

    void operation3();
}

interface Interface3 {
    void operation4();

    void operation5();
}

class B implements Interface1, Interface2 {

    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 operation2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3");
    }
}

class D implements Interface1, Interface3 {

    @Override
    public void operation1() {
        System.out.println("D 实现了 operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 operation5");
    }
}

/**
 * A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法
 */
class A {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend2(Interface2 i) {
        i.operation2();
    }

    public void depend3(Interface2 i) {
        i.operation3();
    }
}

/**
 * C类通过接口Interface1,Interface3依赖(使用)D类,但是只会用到1,4,5方法
 */
class C {
    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface3 i) {
        i.operation4();
    }

    public void depend5(Interface3 i) {
        i.operation5();
    }
}

3.3 Principio de Inversión de Dependencia (Principio de Inversión de Dependencia)

3.3.1 Introducción

  • 1) Los módulos de alto nivel no deberían depender de los módulos de bajo nivel, ambos deberían depender de su abstracción (interfaz o clase abstracta)
  • 2) Las abstracciones no deben depender de los detalles, los detalles deben depender de las abstracciones
  • 3) La idea central de la inversión de dependencia (inversión) es la programación orientada a la interfaz
  • 4) El principio de inversión de dependencia se basa en el concepto de diseño: en comparación con la variabilidad de los detalles, las cosas abstractas son mucho más estables . Una arquitectura basada en abstracciones es mucho más estable que una arquitectura basada en detalles. En java, la abstracción se refiere a interfaces o clases abstractas, y los detalles se refieren a clases de implementación concretas.
  • 5) El propósito de usar interfaces o clases abstractas es formular especificaciones sin involucrar ninguna operación específica, y dejar la tarea de mostrar detalles a sus clases de implementación para completar
  • El polimorfismo es una de las formas de implementar el principio de inversión de dependencia.

Caso: la clase de usuario recibe correo electrónico, WeChat y otra información.

Violación de la inversión de dependencia: referencia concreta en lugar de abstracta:

/**
 * 方式1分析
 * 1.简单,比较容易想到
 * 2.如果我们获取的对象是微信,短信等等,则新增类,同时 Peron也要增加相应的接收方法
 * 3.解决思路:
 * 引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖
 * 因为Email,Weixin等等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符号依赖倒转原则
 */
class Email {
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Person {
    public void receive(Email email) {
        System.out.println(email.getInfo());
    }
}

Mejora: forma polimórfica de referirse a las abstracciones:

interface IReceiver {
    String getInfo();
}

class Email implements IReceiver {
    @Override
    public String getInfo() {
        return "电子邮件信息:Hello World!";
    }
}

class Weixin implements IReceiver {
    @Override
    public String getInfo() {
        return "微信消息:Hello World!";
    }
}

class ShortMessage implements IReceiver {
    @Override
    public String getInfo() {
        return "短信信息:Hello World!";
    }
}

class Person {
    public void receive(IReceiver receiver) {
        System.out.println(receiver.getInfo());
    }
}

3.3.2 Tres formas de transferencia de dependencia

  • 1) Los módulos de bajo nivel deben tener clases o interfaces abstractas, o ambas, para una mejor estabilidad del programa
  • 2) El tipo de declaración de la variable debe ser una clase o interfaz abstracta tanto como sea posible , de modo que haya una capa intermedia entre nuestra referencia de variable y el objeto real, lo que conduce a la expansión y optimización del programa.
  • 3) Siga el principio de sustitución de Liskov al heredar

El caso de cambiar de TV: 

1) Transferencia de interfaz

 La interfaz ITV es un parámetro de método común de la interfaz IOpenAndClose

//方式1:通过接口传递实现依赖
//开关的接口
interface IOpenAndClose {
    void open(ITV tv);//抽象方法,接收接口
}
//ITV接口
interface ITV {
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    public void open(ITV tv){
        tv.play();
    }
}

2) Transferencia del método de construcción

 La interfaz ITV es una variable miembro y un parámetro constructor de la clase OpenAndClose 

//方式2:通过构造函数实现依赖
//开关的接口
interface IOpenAndClose {
    void open();//抽象方法
}
//ITV接口
interface ITV {
    public void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose {
    private ITV tv; // 成员
    
    public OpenAndClose(ITV tv){ // 构造器
        this.tv = tv;
    }
    
    public void open(){
        this.tv.play();
    }
}

3) Transferencia a través del colocador

 La interfaz ITV es una variable miembro y un parámetro de método de establecimiento de la clase OpenAndClose  

//方式3,通过setter方法传递
interface IOpenAndClose{
    void open();//抽象方法
    void setTv(ITV tv);
}
//ITV接口
interface ITV{
    void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose{
    private ITV tv;
    public void setTv(ITV tv){
        this.tv=tv;
    }
    public void open(){
        this.tv.play();
    }
}

3.4 Principio de sustitución de Liskov

Pensamiento y explicación de la herencia en el objeto orientado a objetos OO

  • 1) La herencia contiene una capa de significado de este tipo: todos los métodos que se han implementado en la clase principal en realidad establecen especificaciones y contratos. Aunque no obliga a todas las subclases a seguir estos contratos, si la subclase ya ha implementado estos contratos. Cualquier modificación de el método implementado causará daño a todo el sistema de herencia
  • 2) Mientras que la herencia trae conveniencia al diseño del programa, también trae desventajas. Por ejemplo, el uso de herencia traerá intrusión al programa , reducirá la portabilidad del programa y aumentará el acoplamiento entre objetos.Si una clase es heredada por otras clases, cuando esta clase necesita ser modificada, todas las subclases deben ser consideradas , y después de modificar la clase principal, todas las funciones relacionadas con la subclase pueden causar fallas
  • 3) Se plantea la pregunta: ¿Cómo usar correctamente la herencia en programación? => Principio de sustitución de Liskov

introducción básica

  • 1) El principio de sustitución de Liskov fue propuesto por la Sra. Li del MIT en 1988
  • 2) La función permanece inalterada después de que el objeto de tipo padre es reemplazado por el objeto de tipo secundario: si para cada objeto o1 de tipo T1, hay un objeto o2 de tipo T2, de modo que todos los programas P definidos por T1 se sustituyen en todos los objetos o1 El comportamiento del programa P no cambia cuando se convierte en o2, entonces el tipo T2 es un subtipo del tipo T1. En otras palabras, todos los lugares que se refieren a la clase base deben poder usar objetos de su subclase de forma transparente.
  • 3) Cuando use la herencia, siga el principio de sustitución de Liskov e intente no anular el método de la clase principal en la subclase.
  • 4) El principio de sustitución de Liskov nos dice que la herencia en realidad mejora el acoplamiento de dos clases y, en las circunstancias apropiadas, los problemas se pueden resolver mediante la agregación, la composición y la dependencia.

caso:

Solución tradicional: la subclase reescribe el método de resta de la clase principal como un método de suma: la reutilización de todo el sistema de herencia será relativamente pobre.

public void test() {
    A a = new A();
    System.out.println("11-3=" + a.func1(11, 3));
    System.out.println("1-8=" + a.func1(1, 8));
    System.out.println("---------------------");

    B b = new B();
    System.out.println("11-3=" + b.func1(11, 3));
    System.out.println("1-8=" + b.func1(1, 8));
    System.out.println("11+3+9=" + b.func2(11, 3));
}

class A {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends A {
    @Override
    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }
}

Plan de mejora: tanto la clase principal original como la subclase heredan una clase base más popular, se elimina la relación de herencia original y, en su lugar, se utilizan relaciones como la dependencia, la agregación y la combinación.

//创建一个更加基础的基类
class Base {
    //将更基础的成员和方法写到Base类中
}

class A extends Base {
    //返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

class B extends Base {
    //如果B需要使用A类的方法,使用组合关系
    private A a;

    public int func1(int num1, int num2) {
        return num1 + num2;
    }

    //增加了一个新功能:完成两个数相加,然后和9求和
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }

    public int func3(int num1, int num2) {
        return this.a.func1(num1, num2);
    }
}

3.5 Principio abierto cerrado

Activado: Abierto a extensiones.

Cerrado: Cerrado por modificación.

  • 1) El principio de apertura y cierre es el principio de diseño más básico e importante en la programación.
  • 2) Una entidad de software como clases, módulos y funciones debe estar abierta para extensión (para proveedores) y cerrada para modificación (para usuarios). Cree marcos con abstracciones y amplíe los detalles con implementaciones . Después de agregar nuevas funciones, el código original no se ha cambiado.
  • 3) Cuando el software necesite cambiar, intente lograr el cambio extendiendo el comportamiento de la entidad de software en lugar de modificar el código existente.
  • 4) Siga otros principios en la programación, y el propósito de usar patrones de diseño es seguir el principio de apertura y cierre.

caso:

Opción 1: Solución Tradicional

Una función de dibujar gráficos, el diagrama de clases está diseñado de la siguiente manera:

class GraphicEditor {
    public void drawShape(Shape s) {
        if (s.m_type == 1) {
            drawRectangle(s);
        } else if (s.m_type == 2) {
            drawCircle(s);

        } else if (s.m_type == 3) {
            drawTriangle(s);
        }
    }

    public void drawRectangle(Shape r) {
        System.out.println("矩形");
    }

    public void drawCircle(Shape r) {
        System.out.println("圆形");
    }

    public void drawTriangle(Shape r) {
        System.out.println("三角形");
    }
}

class Shape {
    public int m_type;
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }
}

Ventajas y desventajas del método 1

  • 1) La ventaja es que es más fácil de entender, simple y fácil de operar.
  • 2) La desventaja es que viola el principio OCP del patrón de diseño, es decir, está abierto a la extensión (proveedor) y cerrado a la modificación (usuario). Es decir, cuando añadimos nuevas funciones a la clase, intentar no modificar el código, o modificar el código lo menos posible.
  • 3) Por ejemplo, si queremos agregar un nuevo tipo de gráfico en este momento, debemos hacer las siguientes modificaciones, y hay muchas modificaciones 4) Demostración de código

Análisis de ideas de mejora del modo 1

Haga que la clase Shape creada sea una clase abstracta y proporcione un método de dibujo abstracto para que las subclases lo implementen

De esta manera, cuando tenemos un nuevo tipo de gráficos, solo necesitamos dejar que la nueva clase de gráficos herede Shape e implementar el método de dibujo.

No es necesario modificar el código del usuario y se cumple el principio de apertura y cierre .

Método 2 Principio de apertura y cierre: la función de dibujo se establece como un método abstracto de la clase base, y la nueva clase de implementación solo necesita heredar la clase base e implementar el método abstracto de dibujo.

class GraphicEditor {
    public void drawShape(Shape s) {
        s.draw();
    }
}

abstract class Shape {
    int m_type;

    public abstract void draw();
}

class RectangleShape extends Shape {
    RectangleShape() {
        m_type = 1;
    }

    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

class CircleShape extends Shape {
    CircleShape() {
        m_type = 2;
    }

    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

class TriangleShape extends Shape {
    TriangleShape() {
        m_type = 3;
    }

    @Override
    public void draw() {
        System.out.println("三角形");
    }
}

3.6 Principio de Deméter

introducción básica

  • 1) Un objeto debe mantener un conocimiento mínimo sobre otros objetos.
  • 2) Cuanto más estrecha sea la relación entre clases y clases, mayor será el grado de acoplamiento
  • 3) La ley de Dimit también se denomina principio de mínimo conocimiento , es decir, cuanto menos sepa una clase sobre las clases de las que depende, mejor. Es decir, no importa cuán compleja sea la clase dependiente, intente encapsular la lógica dentro de la clase. Excepto por el método público provisto, ninguna información será revelada al mundo exterior.
  • 4) También hay una definición más simple de la ley de Deméter: solo comunícate con amigos directos
  • 5) Amigos directos: Cada objeto tiene una relación de acoplamiento con otros objetos, siempre que haya una relación de acoplamiento entre dos objetos, decimos que los dos objetos son amigos. Hay muchas formas de acoplamiento: dependencia, asociación, composición, agregación, etc. Entre ellos, llamamos amigos directos a las clases que aparecen en las variables miembro, parámetros de método y valores de retorno del método , mientras que las clases que aparecen en las variables locales no son amigos directos. Es decir, es mejor que las clases desconocidas no aparezcan dentro de la clase en forma de variables locales.

Notas y detalles

  • En la clase principal A, la clase B es un amigo directo en el método, luego la clase B en todas las variables locales del método de la clase A es un amigo directo.
  • 1) El núcleo de la ley de Dimit es reducir el acoplamiento entre clases
  • 2) Pero tenga en cuenta: dado que se reducen las dependencias innecesarias para cada clase, la ley de Dimit solo requiere reducir la relación de acoplamiento entre clases (entre objetos), sin requerir dependencias en absoluto

el caso

Solución tradicional:

1) Hay una escuela con varios colegios y sedes debajo de ella, y ahora se requiere imprimir la identificación del personal de la sede de la escuela y la identificación del personal de la universidad.

//总部员工
class Employee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//学院员工
class CollegeEmployee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

//学院员工管理 类
class CollegeManager {
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<>();    //是直接朋友
        CollegeEmployee collegeEmployee;    //是直接朋友
        for (int i = 0; i < 10; i++) {
            collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("学院员工id=" + i);
            list.add(collegeEmployee);
        }
        return list;
    }
}

//总部员工管理类
class SchoolManager {
    public List<Employee> getAllEmployee() {
//仅出现成员变量,方法参数,方法返回值中的类为直接的朋友
        List<Employee> list = new ArrayList<>();    //Employee 是直接朋友,出现在返回值
        Employee employee;    //是直接朋友
        for (int i = 0; i < 5; i++) {
            employee = new Employee();
            employee.setId("总部员工id=" + i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(CollegeManager sub) {
        System.out.println("--------------学院员工--------------");
        List<CollegeEmployee> list1 = sub.getAllEmployee();    //不是直接朋友,出现在局部变量
        for (CollegeEmployee collegeEmployee : list1) {
            System.out.println(collegeEmployee.getId());
        }
        System.out.println("---------------总部员工-------------");
        List<Employee> list2 = this.getAllEmployee();
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}

Mejora de ejemplo de aplicación

  • 1) El problema con el diseño anterior es que en SchoolManager, la clase CollegeEmployee no es amiga directa de la clase SchoolManager (análisis)
  • 2) De acuerdo con la ley de Dimit, se debe evitar este tipo de relación de amistad indirecta en las clases.
  • 3) Mejore el código de acuerdo con la ley de Dimit y encapsule las variables de objetos locales en parámetros.
//学院员工管理类
class CollegeManager {
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<>();    //是直接朋友
        CollegeEmployee collegeEmployee;    //是直接朋友
        for (int i = 0; i < 10; i++) {
            collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId("学院员工id=" + i);
            list.add(collegeEmployee);
        }
        return list;
    }
//改进,新增方法,输出学院员工信息
    public void printEmployee(){
        System.out.println("--------------学院员工--------------");
 //CollegeEmployee是直接朋友,出现在上面getAllEmployee()方法的返回值
        List<CollegeEmployee> list1 = getAllEmployee();   
        for (CollegeEmployee collegeEmployee : list1) {
            System.out.println(collegeEmployee.getId());
        }        
    }    
}
//总部员工管理类
class SchoolManager {
    public List<Employee> getAllEmployee() {
//仅出现成员变量,方法参数,方法返回值中的类为直接的朋友
        List<Employee> list = new ArrayList<>();    //Employee 是直接朋友,出现在返回值
        Employee employee;    //是直接朋友
        for (int i = 0; i < 5; i++) {
            employee = new Employee();
            employee.setId("总部员工id=" + i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(CollegeManager sub) {
        sub.printEmployee();    //改进,降低耦合,不用非直接朋友。
        System.out.println("---------------总部员工-------------");
        List<Employee> list2 = this.getAllEmployee();
        for (Employee employee : list2) {
            System.out.println(employee.getId());
        }
    }
}

3.7 Principio de reutilización de compuestos

introducción básica

El principio es tratar de usar síntesis/agregación en lugar de herencia.

Es decir, la clase que debe usarse se usa como parámetro, variable miembro y variable local de esta clase.

  • Dependencia : Se refiere a la situación en la que un objeto utiliza otro objeto . Por lo general, se pasa otro objeto como parámetro en un método de un objeto, o se crea una instancia de otro objeto en un método. Una dependencia es una relación de referencia "de corta duración" que se puede liberar una vez que el objeto dependiente ya no es necesario.

  • Composición : Se refiere a una relación de contención y contenido entre dos o más objetos . El objeto contenedor es un todo, y el objeto contenido es un componente . Sus ciclos de vida son consistentes y no pueden existir solos. Por ejemplo, un automóvil se compone del motor, las ruedas, el chasis, etc., y estos componentes tienen el mismo ciclo de vida que el automóvil en general en el que se combinan. Cuando el todo muere, todas las partes mueren con él.

  • Agregación ( Aggregation ): se refiere a una relación entre dos o más objetos que contiene y está contenido, y el objeto contenido puede existir entre múltiples objetos que lo contienen. En una relación de agregación, el objeto contenido puede existir independientemente del objeto contenedor y el ciclo de vida no es necesariamente el mismo. Por ejemplo, una universidad se compone de departamentos, facultades, bibliotecas, etc. Estas partes pueden existir de forma independiente y también pueden pertenecer a diferentes universidades. Incluso si mueren universidades enteras, todavía pueden existir.

En conclusión, la dependencia, la composición y la agregación son conceptos importantes para describir las relaciones de objetos en la programación orientada a objetos y ayudan a diseñar e implementar aplicaciones con buena escalabilidad y mantenibilidad.

el caso

Si la clase B quiere usar el método de la clase A, si hereda directamente, entonces se mejorará el acoplamiento y luego la clase B tendrá que modificarse después de que se modifique la clase A.

Solución:

  • Utilice la clase A como parámetro formal del método ordinario de la clase B;
  • Use la clase A como una variable miembro de la clase B y use el método setter
  • Cree un objeto de clase A en el método ordinario de clase B;

4. Diagrama de clases UML: lenguaje de modelado unificado

  • 1) UML: lenguaje de modelado unificado UML ( lenguaje de modelado unificado ), es una herramienta de lenguaje para el análisis y diseño de sistemas de software, se utiliza para ayudar a los desarrolladores de software a pensar y registrar los resultados del pensamiento.
  • 2) UML en sí mismo es un conjunto de símbolos , al igual que los símbolos matemáticos y los símbolos químicos, estos símbolos se utilizan para describir los diversos elementos del modelo de software y la relación entre ellos, como clases, interfaces, implementaciones, generalizaciones, dependencias, Composición , agregación, etc.
  • 3) Use UML para modelar, las herramientas comúnmente utilizadas son Rational Rose, y algunos complementos también se pueden usar para modelar

diagrama de clases UML

  • 1) Se utiliza para describir la composición de la clase (objeto) en sí misma en el sistema y varias relaciones estáticas entre clases (objetos)
  • 2) Relaciones entre clases: dependencia, generalización (herencia), implementación, asociación, agregación y composición

 

4.1, diagrama de clases - dependencia (dependencia)

Siempre que se utilicen unos a otros en la clase, existe una relación de dependencia entre ellos. Si no hay otra parte, ni siquiera la compilación pasará.

  • 1) La otra parte se utiliza en la clase
  • 2) Atributos de los miembros de la clase
  • 3) El tipo de retorno del método
  • 4) El tipo de parámetro recibido por el método
  • 5) Utilizado en el método 

A usa B, luego A depende de B, es decir, A------->B:

 

4.2, diagrama de clases - generalización (Generalización)

Una relación de generalización es en realidad una relación de herencia, que es un caso especial de una relación de dependencia.

A hereda de B, entonces A➞B

4.3, diagrama de clases - implementación (Implementación)

La relación de implementación es en realidad que la clase A implementa la clase B, que es un caso especial de relación de dependencia 

A implementa B, entonces A➟ B

 

4.4, diagrama de clases - asociación (Association)

La relación de asociación es en realidad la conexión entre clases , que es un caso especial de relación de dependencia.

  • Las asociaciones son navegables: bidireccionales o unidireccionales
  • La relación tiene multiplicidad: como "1" (que significa que hay y solo uno), "0..." (que significa 0 o más), "0,1" (que significa 0 o uno), "n.. .m " (lo que significa que se puede usar de n a m), "m...*" (lo que significa al menos m)

relación unidireccional uno a uno

public class Person {
	private IDCard card;
}
public class IDCard {}

diagrama de clases UML

relación bidireccional uno a uno

public class Person {
	private IDCard card;
}
public class IDCard {
	private Person person;
}

diagrama de clases UML

4.5, diagrama de clases - agregación (Agregación)

La relación de agregación representa la relación entre el todo y la parte , y el todo y la parte pueden separarse . Una relación de agregación es un caso especial de una relación de asociación, por lo que tiene asociada la navegación y la multiplicidad.

B es una variable miembro no instanciada de A, entonces B——◇A

Por ejemplo: una computadora se compone de un teclado, un monitor, un mouse, etc.; cada accesorio que compone una computadora se puede separar de la computadora, y se representa con una línea continua con un rombo hueco:

public class Mouse {}
public class Monitor {}
public class Computer {
    private Mouse mouse;
    private Monitor monitor;

    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }

    public void setMonitor(Monitor monitor) {
        this.monitor = monitor;
    }
}

diagrama de clases UML

4.6, diagrama de clases - composición (Composición)

La relación de combinación es también la relación entre el todo y la parte , pero el todo y la parte no se pueden separar.

B es una variable miembro instanciada de A, entonces B<——◇A

Si pensamos que el mouse, el monitor y la computadora son inseparables, actualice a una relación compuesta

public class Mouse {}
public class Monitor {}
public class Computer {
    private Mouse mouse = new Mouse();
    private Monitor monitor = new Monitor();
}

diagrama de clases UML

Veamos otro caso, en el programa definimos entidades: Persona, IDCard y Head, entonces Head y Person son combinaciones, y IDCcard y Person son agregaciones.

public class IDCard{}
public class Head{}
public class Person{
    private IDCard card;
    private Head head = new Head();
}

diagrama de clases UML

Pero si en el programa, la entidad Persona define una eliminación en cascada de IDCard, es decir, al eliminar Persona, se elimina junto con IDCard, luego IDCard y Person se combinan

 

 

Supongo que te gusta

Origin blog.csdn.net/qq_40991313/article/details/130403757
Recomendado
Clasificación