Sete princípios de design orientado a objetos (incluindo princípios SOLID)

visão geral

O princípio SOLID são as 5 primeiras iniciais escritas nos sete princípios a seguir

1. 单一职责原则(Single Responsibility Principle)
2. 开闭原则(Open Close Principle)
3. 里氏替换原则(Liskov Substitution Principle)
4. 接口隔离原则(Interface Segregation Principle)
5. 依赖倒置原则(Dependence Inversion Principle)
6. 迪米特法则(Law Of Demeter)
7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)

1. Um único princípio

O Princípio da Singularidade afirma que, se você tiver vários motivos para alterar uma classe, deve separar esses motivos para a alteração e dividir a classe em várias classes, cada uma responsável por apenas uma alteração. Quando você faz algum tipo de alteração, você só precisa modificar a classe responsável por lidar com essa alteração. Quando alteramos uma classe com múltiplas responsabilidades, isso pode afetar outras funções da classe.

Ou seja, uma classe não deve conter muitas funções, e tentar completar apenas uma única função, para que quando você modificar uma classe, ela não afete outras funções e evite a correção de erros. Por exemplo, para uma classe A com múltiplas funções, quando você precisa modificar a classe A por causa da função de a1, você também precisa modificar a classe A por causa da função de a2, o que viola o princípio único, porque quando você repara o função de a1, você pode A função de a2 é afetada. Pelo contrário, se a função de a2 for reparada, a função de a1 pode ser afetada. Como em uma classe, um método público do qual a1 e a2 dependem pode ser modificado, é fácil causar o problema acima.

Além disso, um método só é responsável por lidar com uma coisa.

O Princípio de Responsabilidade Única representa uma ótima maneira de identificar classes ao projetar aplicativos e lembra você de pensar em todas as maneiras pelas quais uma classe pode evoluir. Uma boa separação de responsabilidades só pode ser alcançada com um bom entendimento de como o aplicativo funciona.

2. Princípio da Substituição de Liskov

Apenas um projeto OO que satisfaça as duas condições a seguir pode ser considerado para satisfazer o princípio LSP:

  • (1) As condições para julgar os tipos de classes derivadas, como if/else, não devem aparecer no código.

  • (2) A classe derivada deve ser capaz de substituir a classe base e aparecer em qualquer lugar que a classe base possa aparecer, ou se substituirmos o local onde a classe base é usada no código por sua classe derivada, o código ainda poderá funcionar normalmente.

    De um modo geral, o princípio de substituição de Liskov é: as subclasses podem estender as funções da classe pai, mas não podem alterar as funções originais da classe pai. Ou seja: quando uma subclasse herda a classe pai, além de adicionar novos métodos para completar as novas funções, tente não reescrever os 非抽象métodos da classe pai.

O princípio de substituição de Lie requer o seguinte:

  • As subclasses podem estender as funções da classe pai, mas não podem alterar as funções originais da classe pai.

    Ou seja, as subclasses podem implementar os métodos abstratos da classe pai, mas não podem substituir os métodos não abstratos da classe pai.

  • As subclasses podem adicionar seus próprios métodos exclusivos.

  • Quando o método da subclasse substitui o método da classe pai, as pré-condições do método (ou seja, os parâmetros de entrada/entrada do método) são mais flexíveis do que os parâmetros de entrada do método da classe pai.

  • Quando um método de uma subclasse implementa um método da classe pai (sobrecarregando/substituindo ou implementando um método abstrato), as pós-condições (ou seja, o valor de saída/retorno do método) são mais rígidas ou iguais às da classe pai.

Vantagens e desvantagens do princípio de substituição de Liskov:

vantagem:

  • Compartilhamento de código, ou seja, código comum é extraído para a classe pai, melhorando a reutilização do código.
  • As subclasses podem ter suas próprias características com base na classe pai. Melhore a escalabilidade do código.

deficiência:

  • invasivo. Uma vez herdadas, todas as propriedades e métodos da classe pai são propriedade da subclasse
  • vinculativo. A subclasse precisa ter as propriedades e métodos da classe pai, e a subclasse tem algumas restrições.
  • Acoplamento. Quando a classe pai é modificada, a modificação da subclasse precisa ser considerada.

Portanto, o princípio de substituição de Liskov não incentiva o uso de herança, mas quando você precisa usar herança, é necessário adicionar algumas restrições para evitar efeitos adversos. Por exemplo, não pode afetar a função original da classe pai

3. Princípio de Inversão de Dependência

O princípio da inversão de dependência é incentivar o uso de interfaces

O que é dependência? : Na programação, se um módulo a usa/chama outro módulo b, dizemos que o módulo a depende do módulo b.

Módulos de alto nível e módulos de baixo nível: Muitas vezes em uma aplicação, temos algumas classes de baixo nível que implementam algumas operações básicas ou primárias, que chamamos de módulos de baixo nível; também existem algumas classes de alto nível, essas classes Encapsula alguma lógica complexa e depende de classes de baixo nível, que chamamos de módulos de alto nível.

Inversão de Dependência (Inversão de Dependência):

Na programação orientada a objetos, as dependências são invertidas em relação à programação procedural (estruturada). Porque na programação estruturada tradicional, os módulos de alto nível sempre dependem dos módulos de baixo nível.

Uma interface abstrata é uma abstração de um módulo de baixo nível, e o módulo de baixo nível herda ou implementa a interface abstrata.

Desta forma, os módulos de alto nível não dependem diretamente dos módulos de baixo nível, mas dependem da camada de interface abstrata. A interface abstrata também não depende dos detalhes de implementação dos módulos de baixo nível, mas os módulos de baixo nível dependem (herdam ou implementam) da interface abstrata.

O relacionamento entre as classes é estabelecido através da camada de interface abstrata.

Por exemplo, a classe AutoSystem é uma classe de alto nível que faz referência a HondaCar e FordCar. As duas últimas classes Car são classes de baixo nível. O programa pode, de fato, realizar direção não tripulada para carros Ford e Honda:

public class HondaCar{
    
    
    public void Run(){
    
    
        Console.WriteLine("本田开始启动了");
    }
    public void Turn(){
    
    
        Console.WriteLine("本田开始转弯了");
    }
    public void Stop(){
    
    
        Console.WriteLine("本田开始停车了");
    }
}
public class FordCar{
    
    
    public void Run(){
    
    
        Console.WriteLine("福特开始启动了");
    }
    public void Turn(){
    
    
        Console.WriteLine("福特开始转弯了");
    }
    public void Stop(){
    
    
        Console.WriteLine("福特开始停车了");
    }
}

public class AutoSystem{
    
    
    public enum CarType{
    
    
        Ford,Honda
    };
    private HondaCar hcar=new HondaCar();
    private FordCar fcar=new FordCar();
    private CarType type;
    public AutoSystem(CarType type){
    
    
        this.type=type;
    }
    public void RunCar(){
    
    
        if(type==CarType.Ford){
    
    
            fcar.Run();
        } else {
    
    
            hcar.Run();
        }
    }
    public void TurnCar(){
    
    
        if(type==CarType.Ford){
    
    
            fcar.Turn();
        } else {
    
     
            hcar.Turn();
        }
    }
    public void StopCar(){
    
    
        if(type==CarType.Ford){
    
    
            fcar.Stop();
            } else {
    
    
                hcar.Stop();
            }
    }
}

Mas o software está em constante mudança e os requisitos de software estão em constante mudança.

Suposição: os negócios da empresa se expandiram e ela se tornou parceira medalha de ouro da GM, Mitsubishi e Volkswagen, então a empresa exige que o sistema de direção automática também possa ser instalado nos carros produzidos por essas três empresas. Então tivemos que mudar o AutoSystem:

   public class AutoSystem {
    
    
        public enum CarType {
    
    
            Ford, Honda, Bmw
        }

       
        HondaCar hcar = new HondaCar();   //使用new 
        FordCarf car = new FordCar();
        BmwCar bcar = new BmwCar();
        private CarType type;

        public AutoSystem(CarTypetype) {
    
    
            this.type = type;
        }

        public void RunCar() {
    
    
            if (type == CarType.Ford) {
    
    
                fcar.Run();
            } else if (type == CarType.Honda) {
    
    
                hcar.Run();
            } else if (type == CarType.Bmw) {
    
    
                bcar.Run();
            }
        }

        public void TurnCar() {
    
    
            if (type == CarType.Ford) {
    
    
                fcar.Turn();
            } else if (type == CarType.Honda) {
    
    
                hcar.Turn();
            } else if (type == CarType.Bmw) {
    
    
                bcar.Turn();
            }
        }

        public void StopCar() {
    
    
            if (type == CarType.Ford) {
    
    
                fcar.Stop();
            } else if (type == CarType.Honda) {
    
    
                hcar.Stop();
            } else if (type == CarType.Bmw) {
    
    
                bcar.Stop();
            }
        }
    }

Análise: Isso adiciona novas interdependências ao sistema. Com o passar do tempo, mais e mais tipos de carros devem ser adicionados ao AutoSystem, este módulo "AutoSystem" ficará confuso com instruções if/else e dependerá de muitos módulos de baixo nível, desde que os módulos de baixo nível mudem, AutoSystem deve ser alterado de acordo

Então, como resolver isso? Na forma de interface, o sistema AutoSystem depende da abstração de ICar, mas não tem nada a ver com os detalhes de implementação específicos de HondaCar, FordCar e BMWCar, portanto, alterações nos detalhes de implementação não afetarão o AutoSystem. Quanto aos detalhes da implementação, é necessário apenas implementar o ICar, ou seja, os detalhes da implementação dependem da abstração do ICar.

    public interface ICar
    {
    
    
        void Run();
        void Turn();
        void Stop();
    }
    public class BmwCar:ICar
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("宝马开始启动了");
        }
        public void Turn()
        {
    
    
            Console.WriteLine("宝马开始转弯了");
        }
        public void Stop()
        {
    
    
            Console.WriteLine("宝马开始停车了");
        }
    }
    public class FordCar:ICar
    {
    
    
        publicvoidRun()
        {
    
    
            Console.WriteLine("福特开始启动了");
        }
        public void Turn()
        {
    
    
            Console.WriteLine("福特开始转弯了");
        }
        public void Stop()
        {
    
    
            Console.WriteLine("福特开始停车了");
        }
    }
    public class HondaCar:ICar
    {
    
    
        publicvoidRun()
        {
    
    
            Console.WriteLine("本田开始启动了");
        }
        public void Turn()
        {
    
    
            Console.WriteLine("本田开始转弯了");
        }
        public void Stop()
        {
    
    
            Console.WriteLine("本田开始停车了");
        }
    }
    public class AutoSystem
    {
    
    
        private ICar icar;
        public AutoSystem(ICar icar)    //使用构造函数作为入参,不再使用new创建具体的CaR实例
        {
    
    
            this.icar=icar;
        }
        private void RunCar()
        {
    
    
            icar.Run();
        }
        private void TurnCar()
        {
    
    
            icar.Turn();
        }
        private void StopCar()
        {
    
    
            icar.Stop();
        }
    }

A aplicação desse princípio significa que as classes de nível superior não usam diretamente as classes de nível inferior, elas usam interfaces como uma camada de abstração. Nesse caso, o código para criar um objeto da classe subjacente na classe superior não pode usar diretamente o operador new.Por exemplo, no exemplo acima, AutoSystem finalmente usa o construtor para passar em uma instância de Car. Alguns padrões de design criacional podem ser usados, como método de fábrica, fábrica abstrata e padrão de protótipo. 模版设计模式是应用依赖倒转原则的一个例子. Obviamente, usar esse padrão requer esforço extra e código mais complexo, mas pode levar a um design mais flexível. Este princípio não deve ser usado indiscriminadamente, e se tivermos uma classe cuja funcionalidade tem uma alta probabilidade de não mudar no futuro, então não precisamos usá-la.

Modo de modelo, consulte [Modo de design] A diferença entre o modo de estratégia e o modo de modelo , JDBCTemplate, RedisTemplate, MongoTemplate, etc. são modos de modelo típicos

4. Princípio de Segregação de Interface (Princípio de Segregação de Interface, ISP)

Os usuários não podem ser forçados a confiar em interfaces que não usam.

Princípios de design de interface: O design de interface deve seguir o princípio da menor interface e não incluir métodos que os usuários não usam na mesma interface. Se o método de uma interface não for usado, significa que a interface é muito gorda e deve ser dividida em várias interfaces funcionais.

O princípio de segregação de interface afirma que os clientes não devem ser forçados a implementar algumas interfaces que não usarão e devem agrupar os métodos em uma interface gorda e substituí-la por várias interfaces, cada uma atendendo a um submódulo.

Se você projetou uma interface gorda, pode usar o padrão adaptador para isolá-la. Como outros princípios de design, o princípio de segregação de interface requer tempo e esforço adicionais e aumenta a complexidade do código, mas pode resultar em um design mais flexível. Se o usarmos excessivamente, ele gerará um grande número de interfaces contendo um único método; portanto, precisamos usá-lo com base na experiência e identificar os códigos que precisam ser estendidos no futuro.

De um modo geral, o princípio da separação de interfaces incentiva o uso de interfaces, mas certas restrições são impostas para melhor desempenhar o papel de interfaces!

Por exemplo, existem muitas interfaces no ouvinte de eventos do componente swing. Quando você deseja implementar um determinado evento, deve implementar todas as interfaces e pode usar o adaptador WindowAdapter para evitar essa situação:

Interface geral do evento:

 public interface EventListener {
    
    
 }


WindowListener estende essa interface e há muitos métodos abstratos:

public interface WindowListener extends EventListener {
    
    
    /**
     * Invoked the first time a window is made visible.
     */
    public void windowOpened(WindowEvent e);

    public void windowClosing(WindowEvent e);

    public void windowClosed(WindowEvent e);

    public void windowIconified(WindowEvent e);

    public void windowDeiconified(WindowEvent e);

    public void windowActivated(WindowEvent e);

    public void windowDeactivated(WindowEvent e);
}

WindowAdapter é uma classe abstrata. Mas nesta classe abstrata 没有抽象方法:

public abstract class WindowAdapter
    implements WindowListener, WindowStateListener, WindowFocusListener
{
    
    
    /**
     * Invoked when a window has been opened.
     */
    public void windowOpened(WindowEvent e) {
    
    }

    public void windowClosing(WindowEvent e) {
    
    }

    public void windowClosed(WindowEvent e) {
    
    }

    public void windowIconified(WindowEvent e) {
    
    }

    public void windowDeiconified(WindowEvent e) {
    
    }

    public void windowActivated(WindowEvent e) {
    
    }

    public void windowDeactivated(WindowEvent e) {
    
    }

    public void windowStateChanged(WindowEvent e) {
    
    }

    public void windowGainedFocus(WindowEvent e) {
    
    }

    /**
     * Invoked when the Window is no longer the focused Window, which means
     * that keyboard events will no longer be delivered to the Window or any of
     * its subcomponents.
     *
     * @since 1.4
     */
    public void windowLostFocus(WindowEvent e) {
    
    }
}

5. Lei de Deméter

Definição: Se duas entidades de software não precisam se comunicar diretamente, uma chamada mútua direta não deve ocorrer e a chamada pode ser encaminhada por meio de terceiros. Sua finalidade é reduzir o grau de acoplamento entre as classes e melhorar a relativa independência dos módulos.

A Lei de Deméter também é chamada de Princípio do Menor Conhecimento.De um modo geral, significa que quanto menos uma classe souber sobre as classes das quais depende, melhor. Ou seja, para a classe dependente, não importa quão complexa seja a lógica, tente encapsular a lógica dentro da classe o máximo possível e não vaze nenhuma informação para o mundo externo, exceto os métodos públicos fornecidos.

Existe uma definição ainda mais simples da Lei de Deméter: comunique-se apenas com amigos próximos. Primeiro, vamos explicar o que é um amigo direto: Chamamos as classes que aparecem em variáveis-membro, parâmetros de métodos e valores de retorno de métodos de amigos diretos, enquanto as classes que aparecem em variáveis ​​locais não são amigos diretos. Ou seja, é melhor que classes desconhecidas não apareçam dentro da classe como variáveis ​​locais.

O objetivo da lei de Dimit é reduzir o acoplamento entre as classes. Como cada classe minimiza sua dependência de outras classes, é fácil tornar os módulos funcionais do sistema independentes uns dos outros e não há relação de dependência entre eles.

Uma possível consequência da aplicação da lei de Dimit é que existe um grande número de classes intermediárias no sistema, que existem apenas para transferir o relacionamento de chamada mútua entre as classes, o que aumenta em certa medida a complexidade do sistema.

O modo fachada (Fachada) e o modo intermediário (Mediador) no modo projeto são exemplos de aplicação da lei de Dimit.

Desvantagens da lei de Dimit no sentido estrito:

  • Seguir a lei de Dimit entre as classes simplificará o projeto local de um sistema, pois cada parte não estará associada a objetos distantes. No entanto, isso também reduz a eficiência da comunicação entre os diferentes módulos do sistema e também dificulta a coordenação entre os diferentes módulos do sistema.

A incorporação da lei de Deméter generalizada no design da classe:

  • Tente reduzir os direitos de acesso de uma classe.
  • Mantenha os privilégios de acesso dos membros o mais baixo possível.

exemplo

A empresa deseja imprimir as informações pessoais de um determinado departamento, o código é o seguinte, um contra-exemplo:

/**
 * 雇员
 */
public class Employee {
    
    
    private String name;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
}
/**
 * 部门经理
 */
public class Manager {
    
    
    public List<Employee> getEmployees(String department) {
    
    
        List<Employee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            Employee employee = new Employee();
            // 雇员姓名
            employee.setName(department + i);
            employees.add(employee);
        }
        return employees;
    }
}
/**
 * 公司
 */
public class Company {
    
    
	private Manager manager = new Manager();

    public void printEmployee(String name){
    
    
        List<Employee> employees = manager.getEmployees(name);
        for (Employee employee : employees) {
    
    
            System.out.print(employee.getName() + ";");
        }
    }
}

O printEmployee na classe Company imprime com sucesso as informações pessoais, mas a classe Employee aparece apenas no método printEmployee() como uma variável local e é um amigo indireto da classe Company (só se comunica com amigos diretos 违背了迪米特法则)

A forma correta é a seguinte : Coloque o método de impressão das informações do funcionário na classe Empresa na classe Gerente, e só chame o método printEmployee() no Gerenciador na Empresa, e a classe Funcionário não aparecerá mais na classe Empresa

/**
 * 雇员
 */
public class Employee {
    
    
    private String name;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
}
/**
 * 部门经理
 */
public class Manager {
    
    
    public List<Employee> getEmployees(String department) {
    
    
        List<Employee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            Employee employee = new Employee();
            // 雇员姓名
            employee.setName(department + i);
            employees.add(employee);
        }
        return employees;
    }

    public void printEmployee(String name){
    
    
        List<Employee> employees = this.getEmployees(name);
        for (Employee employee : employees) {
    
    
            System.out.print(employee.getName() + ";");
        }
    }
}
/**
 * 公司
 */
public class Company {
    
    
    private Manager manager = new Manager();

    public void printEmployee(String name){
    
    
        manager.printEmployee(name);
    }
}

Você pode até definir as permissões da classe Funcionário para serem visíveis apenas para o Gerente, não para a Empresa

6. Princípio de reutilização de combinação/agregação

Agregação significa a relação entre o todo e a parte, e significa "contém". O todo é composto de partes, e a parte pode existir como um indivíduo independente sem o todo.
A combinação é uma agregação mais forte.As partes formam um todo e são inseparáveis.As partes não podem existir independentemente do todo. Em um relacionamento composto, as partes têm o mesmo ciclo de vida do todo, e o novo objeto composto tem controle total sobre suas partes constituintes, incluindo sua criação e destruição.

Composição/agregação e herança são duas formas básicas de conseguir a reutilização. O princípio da reutilização sintética refere-se ao uso de composição/agregação tanto quanto possível, em vez de herança. Os relacionamentos de herança só devem ser usados ​​quando todas as seguintes condições forem atendidas:

  • Uma subclasse é um tipo especial de superclasse, não um papel de uma superclasse, que é distinguir entre "Has-A" e "Is-A". Somente o relacionamento "Is-A" está em conformidade com o relacionamento de herança, e o Relacionamentos "Has-A" devem ser descritos usando agregações.
  • Nunca deve haver a necessidade de substituir uma subclasse por uma subclasse de outra classe. Se você não tem certeza se ela se tornará outra subclasse no futuro, não use herança.
  • Uma subclasse tem a responsabilidade de estender a superclasse, não de substituir ou cancelar o registro da superclasse. Se uma subclasse precisa substituir substancialmente o comportamento da superclasse, então a classe não deve ser uma subclasse da superclasse.

Diferença entre composição e agregação

7. O princípio de abertura e fechamento

O princípio de abertura e fechamento é o capítulo geral dos outros seis princípios. Colocamos deliberadamente no final. Ou seja, quando seu código atende aos 6 primeiros princípios, basicamente atende ao princípio de abertura e fechamento!

  • Aberto para extensão ------- O comportamento do módulo pode ser estendido para atender a novos requisitos.
  • Fechado para modificação ------- Não permitir modificação do código-fonte do módulo (ou tentar minimizar a modificação)

O princípio aberto-fechado diz que devemos nos esforçar para projetar módulos que não precisem ser modificados. Em aplicações práticas, o código alterado é isolado do código que não precisa ser alterado, o código alterado é abstraído em uma interface estável e a programação é realizada na interface. Ao estender o comportamento do sistema, precisamos apenas adicionar um novo código sem modificar o código existente. Isso geralmente pode ser obtido adicionando novas subclasses e substituindo métodos da classe pai.

开闭原则是面向对象设计的核心,满足该原则可以达到最大限度的复用性和可维护性

referência

7 Princípios do Projeto Orientado a Objetos
7 Princípios do Projeto Orientado a Objetos
Desenvolvimento de Software: Os Sete Princípios do Projeto Orientado a Objetos!
Princípio de Substituição de Liskov de Princípios de Design Orientado a Objetos com Definição em Inglês

Acho que você gosta

Origin blog.csdn.net/m0_45406092/article/details/129697093
Recomendado
Clasificación