欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式之一:六大设计原则

程序员文章站 2024-03-16 22:18:10
...

设计模式之一:六大设计原则

这里有篇文章关于各个原则结合代码进行较为清晰的阐述:http://www.uml.org.cn/sjms/201211023.asp#4

个人觉得其实六大原则中,单一职责原则、开放-封闭原则其实是模块化编程时代的思想顺延,这两个原则显然是经过经验积累得到的代码可维护性的保证。而关于面向对象编程的三个重要思想则是面向接口编程(中间抽象层)、合成/聚合原则(减少不必要的强耦合)以及迪米特原则(增加中间代理对象以减少模块间的耦合)。下面进行单独介绍。

核心原则之一:依赖倒转原则
设计模式之一:六大设计原则

问题由来:客户端client直接依赖类A,假如client要改为依赖类B,则必须通过修改client的代码来达成。类A和类B是低层模块,负责基本的原子操作; client一般负责具体的业务逻辑,本就负担繁重,如果底层不能保持统一,任何一次底层模块变动都要惊扰client,这显然是不合适的。

解决方案:添加中间接口层I,将client修改为依赖接口I,类A和类B各自实现接口I,这样client通过接口I间接与类A或者类B发生联系,则会大大降低直接修改client的几率。

核心原则之二:迪米特原则
“高内聚,低耦合”可以说是现在模式设计中的无上心法,而类与类之间彼此要了解的越多,则两者间的耦合关系越强,这样其中一方发生变化,必然会对另一方造成不必要的冲击影响。所以类与类之间的了解在满足需求的前提下,所需知识应该尽可能地少。

迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

给出一段Java的示例代码进行分析

//总公司员工
class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

//分公司员工
class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //为分公司人员按顺序分配一个ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
}

class CompanyManager{

    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //为总公司人员按顺序分配一个ID
            emp.setId("总公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager sub){
        List<SubEmployee> list1 = sub.getAllEmployee();
         //作为总经理,其实不应该知道分布员工的信息的,甚至连它们被叫做SubEmployee这个名称都不应该知道
        for(SubEmployee e:list1){
            System.out.println(e.getId());
        }

        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

public class Client{
    public static void main(String[] args){
        CompanyManager e = new CompanyManager();
        e.printAllEmployee(new SubCompanyManager());
    }
} 

所以改进的逻辑应该如下
设计模式之一:六大设计原则

即应该讲分布员工的信息枚举功能交给分公司经理来完成,在分公司经理的类中添加一个枚举函数。

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //为分公司人员按顺序分配一个ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printEmployee(){
        List<SubEmployee> list = this.getAllEmployee();
        for(SubEmployee e:list){
            System.out.println(e.getId());
        }
    }
}

/*改写了权限之后,这样总经理的函数操作将会简单并且轻松很多*/
class CompanyManager{
    public List<Employee> getAllEmployee() {...}

    public void printAllEmployee(SubCompanyManager sub) {
        sub.printEmployee();
        //to-do ...
    }
}

当然这里要注意的迪米特原则又可以叫做中间人代理,但是这里的代理是顺手之举,如这里的分公司经理枚举分公司的员工名单,这是顺手做的,而不能为了代理专门设置一个专门的代理类,这是代理模式,但是不应该过分使用,否则会导致存在大量的中介传递类,这种频繁创建对象用于传递信息,无疑也会导致信息传递链条过长。

核心原则之三:合成/聚合原则
在面向对象设计中,合成/聚合关系的耦合强度是明显低于继承关系的,根据”高内聚,低耦合“心法,显然不能盲目使用继承关系,这会导致UML继承树深度和规模急速扩展,故而应该尽可能选择低耦合的关系,如依赖、关联、组合、聚合、委托等,其中合成/聚合原则(CARP:Composition and Aggregation Reuse Principle)。
单一采用继承关系设计的UML图如下

设计模式之一:六大设计原则

采用CARP原则之后,得到的UML图如下
设计模式之一:六大设计原则

采用CARP原则之后,得到的UML图如下
两者对比可以明显发现CARP原则的好处。

除了上述六大原则,还有一些其他领域的原则也可以拿来借鉴,如最小规模接口原则。
最小接口原则定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口分割最小化原则。