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

这里写图片描述

这里有篇文章关于各个原则结合代码进行较为清晰的阐述: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分别与他们需要的接口建立依赖关系。也就是采用接口分割最小化原则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值