热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java设计模式之六大原则分别是什么

这篇文章将为大家详细讲解有关java设计模式之六大原则分别是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收

这篇文章将为大家详细讲解有关java设计模式之六大原则分别是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

Java是什么

Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序。

一、单一职责原则

1、单一职责定义

单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责

变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。

2、单一职责优点

1)降低了类的复杂度。一个类只负责一项职责比负责多项职责要简单得多。

2) 提高了代码的可读性。一个类简单了,可读性自然就提高了。

3) 提高了系统的可维护性。代码的可读性高了,并且修改一项职责对其他职责影响降低了,可维护性自然就提高了。

4) 变更引起的风险变低了。单一职责最大的优点就是修改一个功能,对其他功能的影响显著降低。

3、案例说明

在网上找了个比较好理解,也比较符合实际开发中用来思考的小案例。

有一个用户类,我们先看它的接口:

java设计模式之六大原则分别是什么

这个接口是可以优化的,用户的属性(Property)和用户的行为(Behavior)没有分开,这是一个严重的错误!非常正确,这个接口确实设计得一团糟,应该把用户的信息抽取成一个BO(Bussiness Object,业务对象),把行为抽取成一个BIZ(Business Logic,业务逻辑),按照这个思路对类图进行修正,如图1-2所示。

java设计模式之六大原则分别是什么

重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。

然后IUserInfo来实现这两个接口,重写方法。

代码清单1-1 分清职责后的代码示例

.......
 
IUserBiz userInfo = new UserInfo();
 
//我要赋值了,我就认为它是一个纯粹的BO
 
IUserBO userBO = (IUserBO)userInfo;
 
userBO.setPassword("abc");
 
//我要执行动作了,我就认为是一个业务逻辑类
 
IUserBiz userBiz = (IUserBiz)userInfo;
 
userBiz.deleteUser();
 
.......

思考:上面这样是单一职责原则吗?当然不是了,你实现了两个接口,不还是把行为和属性写在一个类了,和最上面又有什么区别呢,这里只能说实现了接口隔离原则(下面会说)

那如何来确保单一原则,在实际的使用中,我们更倾向于使用两个不同的类:一个是IUserBO, 一个是IUserBiz很简单如图所示:

java设计模式之六大原则分别是什么

4、自己理解

单一职责原则有两个难点:

1) 职责划分:

一个职责一个接口,但问题是“职责”是一个没有量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑。

比如上面写成一个类他的单一职责就是修改用户信息,为什么一定要分修改行为和修改属性。那是不是又可以在细分修改密码和修改属性呢?

2)类的冗余

如果可以追求单一职责也是没有必要的,本来一个类可以搞定的实现,如果非得修改用户名一个类,修改密码一个类来实现单一原则,这样也会让你的类变得非常多,反而不容易维护。

我自己的感悟:

1)首先要培养单一职责的思想,特别是如果代码可以复用的情况下经常思考能不能用单一职责原则来划分类。

2) 类的单一职责实现在好多时候并不切实际,但是方法上一定要保持单一职责原则。比如你修改密码的方法就是用来修改密码。这样做有个很大的好处就是便于代码调试,容易将代码的Bug找出来,一个方法只完成

一件事情,相对调试能简单很多,让其他人员能更快更好的读懂代码、理解这个类或者方法的功能。

二、里氏代换原则

这个和单一职责原则比起来,显然就好理解多了,而且也不那么模糊不清。

1、定义

官方定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。

简单理解就是:子类一般不该重写父类的方法,因为父类的方法一般都是对外公布的接口,是具有不可变性的,你不该将一些不该变化的东西给修改掉。

是不是感觉这个原则不太招人喜欢,因为我们在写代码的时候经常会去重写父类的方法来满足我们的需求。而且在模板方法模式,缺省适配器,装饰器模式等一些设计模式都会采用重写父类的方法。

怎么说呢,里氏代换原则的主要目的主要是防止继承所带来的弊端。

继承的弊端:

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。

继承会增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

2、案例说明

SomeoneClass类,其中有一个方法,调用了某一个父类的方法。

//某一个类
public class SomeoneClass {
    //有某一个方法,使用了一个父类类型
    public void someoneMethod(Parent parent){
        parent.method();
    }
}

父类代码

public class Parent {

    public void method(){
        System.out.println("parent method");
    }
}

SubClass子类把父类的方法给覆盖。

public class SubClass extends Parent{

    //结果某一个子类重写了父类的方法,说不支持该操作了
    public void method() {
        throw new UnsupportedOperationException();
    }
    
}

测试类

/**这个异常是运行时才会产生的,也就是说,我的SomeoneClass并不知道会出现这种情况,结果就是我调用下面这段代码的时候,
 *本来我们的思维是Parent都可以传给someoneMethod完成我的功能,我的SubClass继承了Parent,当然也可以了,但是最终这个调用会抛出异常。
 */
public class Client {

    public static void main(String[] args) {
        SomeoneClass someoneClass = new SomeoneClass();
        someoneClass.someoneMethod(new Parent());
        someoneClass.someoneMethod(new SubClass());
    }
}

这就相当于埋下了一个个陷阱,因为本来我们的原则是,父类可以完成的地方,我用子类替代是绝对没有问题的,但是这下反了,我每次使用一个子类替换一个父类的时候,我还要担心这个

子类有没有给我埋下一个上面这种炸弹。

3、自己理解

感觉自己在开发中不太会出现上面这么愚蠢的错误。理由:

1)自己水平有限,平时在开发中使用继承的时候都是基础API的类然后重写,很少继承自己写的类,一般都是实现接口比较多。

2)第二就算我用了继承,我在传参的时候我只要稍微注意下就应该知道这个方法的参数是Parent,而如果我要放入SubClass时,就应该考虑自己有没有重写这个方法,如果重写这样肯定不行。所以也不多发生上面的错误了。

所以总的来说,要知道继承的这个隐患,在开发中注意就是。

三、接口隔离原则

1、定义

当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。

为什么要这么做呢?

其实很好理解,因为你实现一个接口就是实现它所有的方法,但其实你并不需要它的所有方法,那就会产生:一个类实现了一个接口,里面很多方法都是空着的,只有个别几个方法实现了。

这样做不仅会强制实现的人不得不实现本来不该实现的方法,最严重的是会给使用者造成假象,即这个实现类拥有接口中所有的行为,结果调用方法时却没收获到想要的结果。

2、案例说明

比如我们设计一个手机的接口时,就要手机哪些行为是必须的,要让这个接口尽量的小,或者通俗点讲,就是里面的行为应该都是这样一种行为,就是说只要是手机,你就必须可以做到的。

下面是手机接口。

public interface Mobile {

    public void call();//手机可以打电话
    
    public void sendMessage();//手机可以发短信
    
    public void playBird();//手机可以玩愤怒的小鸟?
    
}

上面第三个行为明显就不是一个手机必须有的,那么上面这个手机的接口就不是最小接口,假设我现在的非智能手机去实现这个接口,那么playBird方法就只能空着了,因为它不能玩。

3、自己理解

这个没啥说的,很好理解,最上面我写单一职责原则的时候的那个案例,中间那部分就是接口隔离原则。这个思想自己要慢慢培养,然后更多的运用到实际开发中去。

四、依赖倒置原则

1、定义

依赖倒置原则包含三个含义

1) 高层模块不应该依赖低层模块,两者都应该依赖其抽象

2) 抽象不应该依赖细节

3)细节应该依赖抽象

2、案例说明

大家都喜欢阅读,阅读文学经典滋润自己的内心心灵,下面是小明同学阅读文学经典的一个类图

java设计模式之六大原则分别是什么

文学经典类

//文学经典类
public class LiteraryClassic{
    //阅读文学经典
    public void read(){
       System.out.println("文学经典阅读,滋润自己的内心心灵");
    }
}

小明类

//小明类
public class XiaoMing{
    //阅读文学经典
    public void read(LiteraryClassic literaryClassic){
        literaryClassic.read();
    }
}

场景类

public class Client{
   public static void main(Strings[] args){
      XiaoMing xiaoming = new XiaoMing();
      LiteraryClassic literaryClassic = new LiteraryClassic();
      //小明阅读文学经典
      xiaoming.read(literaryClassic);
   }

}

看,我们的实现,小明同学可以阅读文学经典了。

小明同学看了一段文学经典后,忽然他想看看看小说来放松一下自己,我们实现一个小说类:

小说类

//小说类
public class Novel{
    //阅读小说
    public void read(){
       System.out.println("阅读小说,放松自己");
    }
}

现在我们再来看代码,发现XiaoMing类的read方法只与文学经典LiteraryClassic类是强依赖,紧耦合关系,小明同学竟然阅读不了小说类。这与现实明显的是不符合的,代码设计的是有问题的。那么问题在那里呢?

我们看小明类,此类是一个高层模块,并且是一个细节实现类,此类依赖的是一个文学经典LiteraryClassic类,而文学经典LiteraryClassic类也是一个细节实现类。这是不是就与我们说的依赖倒置原则相违背呢?

依赖倒置原则是说我们的高层模块,实现类,细节类都应该是依赖与抽象,依赖与接口和抽象类。

为了解决小明同学阅读小说的问题,我们根据依赖倒置原则先抽象一个阅读者接口,下面是完整的uml类图:

java设计模式之六大原则分别是什么

IReader接口:

public interface IReader{
   //阅读
   public void read(IRead read){
       read.read();
   }

}

再定义一个被阅读的接口IRead

public interface IRead{
   //被阅读
   public void read();
}

再定义文学经典类和小说类

文学经典类:

//文学经典类
public class LiteraryClassic implements IRead{
    //阅读文学经典
    public void read(){
       System.out.println("文学经典阅读,滋润自己的内心心灵");
    }
}

小说类

//小说类
public class Novel implements IRead{
    //阅读小说
    public void read(){
       System.out.println("阅读小说,放松自己");
    }
}

再实现小明类

//小明类
public class XiaoMing implements IReader{
    //阅读
    public void read(IRead read){
        read.read();
    }
}

然后,我们再让小明分别阅读文学经典和小说

public class Client{
   public static void main(Strings[] args){
      XiaoMing xiaoming = new XiaoMing();
      IRead literaryClassic = new LiteraryClassic();
      //小明阅读文学经典
      xiaoming.read(literaryClassic);

      IRead novel = new Novel();
      //小明阅读小说
      xiaoming.read(novel);
   }
}

至此,小明同学是可以阅读文学经典,又可以阅读小说了,目的达到了。

为什么依赖抽象的接口可以适应变化的需求?这就要从接口的本质来说,接口就是把一些公司的方法和属性声明,然后具体的业务逻辑是可以在实现接口的具体类中实现的。所以我们当依赖

对象是接口时,就可以适应所有的实现此接口的具体类变化。

3、依赖的三种方法

依赖是可以传递,A对象依赖B对象,B又依赖C,C又依赖D,……,依赖不止。只要做到抽象依赖,即使是多层的依赖传递也无所谓惧。

1)构造函数传递依赖对象

在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入:

//小明类
public class XiaoMing implements IReader{
     private IRead read;
     //构造函数注入
     public XiaoMing(IRead read){
        this.read = read;
     }

    //阅读
    public void read(){
        read.read();
    }
}

2)Setter方法传递依赖对象

在类中通过Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入

//小明类
public class XiaoMing implements IReader{
     private IRead read;
     //Setter依赖注入
     public setRead(IRead read){
        this.read = read;
     }

    //阅读
    public void read(){
        read.read();
    }
}

3)接口声明依赖

在接口的方法中声明依赖对象,在为什么我们要符合依赖倒置原则的例子中,我们采用了接口声明依赖的方式,该方法也叫做接口注入。

4、依赖倒置原则的经验

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。我们在项目中使用这个原则要遵循下面的规则:

1)每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备

2)变量的表面类型尽量是接口或者抽象类

3)任何类都不应该从具体类派生

4)尽量不要覆写基类的方法

如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。

5)结合里氏替换原则使用

依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要方法,在项目中,大家只要记住是”面向接口编程”就基本上是抓住了依赖倒置原则的核心了。

五、迪米特原则

这个原则在开发中还是非常有用的。

1、定义

大致意思是:即一个类应该尽量不要知道其他类太多的东西,不要和陌生的类有太多接触。

迪米特原则还有一个解释:Only talk to your immediate friends(只与直接朋友通信)。

什么叫直接朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系类型有很多,例如:组合,聚合,依赖等。朋友类也可以这样定义:出现在成员

变量,方法的输入输出参数中的类,称为朋友类。

2、案例说明

上体育课,我们经常有这样一个场景:

体育老师上课前要体育委员确认一下全班女生到了多少位,也就是体育委员清点女生的人数。如图:

java设计模式之六大原则分别是什么

分析:这里其实体育老师和体育委员是朋友,因为他们是有业务来源,而女生人数是和体育委员有业务来源(它们是朋友),但是体育老师和女生人数是没有直接业务来源的所以体育老师类中

不应该参杂女生相关信息,这就是迪米特原则

(1)没有才有迪米特原则

体育老师类

public class Teacher{

  //老师对体育委员发一个命令,让其清点女生人数的方法
  public void command(GroupLeader groupLeader){
     List listGirls = new ArrayList();
     //初始化女生,发现老师和女生有耦合
     for(int i=0;i<20;i++){
       listGirls.add(new Girl());
     }
     //告诉体育委员开始清点女生人数
     groupLeader.countGirls(listGirls);
  }
}

体育委员类

public class GroupLeader{
  //清点女生数量
  public void countGirls(List listGirls){
     System.out.println("女生人数是:"+listGirls.size());
  }
}

女生类

publci class Girl{
}

测试类

public class Client{
   public static void main(Strings[] args){
      Teacher teacher = new Teacher();
      //老师给体育委员发清点女生人数的命令
      teacher.command(new GroupLeader());
   }
}

分析:我们再回头看Teacher类,Teacher类只有一个朋友类GroupLeader,Girl类不是朋友类,但是Teacher与Girl类通信了,这就破坏了Teacher类的健壮性,Teacher类的方法竟然与一个不是

自己的朋友类Girl类通信,这是不允许的,严重违反了迪米特原则。

(2)采用迪米特原则

我们对程序进行如下修改,将类图修改如下:

java设计模式之六大原则分别是什么

修改后的老师类:(注意这里面已经没有女生信息了)

public class Teacher{
  //老师对体育委员发一个命令,让其清点女生人数
  public void command(GroupLeader groupLeader){
     //告诉体育委员开始清点女生人数
     groupLeader.countGirls();
  }
}

修改后的体育委员类

public class GroupLeader{
   private List listGirls;
   public GroupLeader(List listGirls){
      this.listGirls = listGirls;
   }
  //清点女生数量
  public void countGirls(){
     System.out.println("女生人数是:"+listGirls.size());
  }
}

修改后的测试类

public class Client{
   public static void main(Strings[] args){
     //产生女生群体
     List listGirls = new ArrayList();
     //初始化女生
     for(int i=0;i<20;i++){
       listGirls.add(new Girl());
     }

      Teacher teacher = new Teacher();
      //老师给体育委员发清点女生人数的命令
      teacher.command(new GroupLeader(listGirls));
   }
}

对程序修改,把Teacher中对Girl群体的初始化移动到场景类中,同时在GroupLeader中增加对Girl的注入,避开了Teacher类对陌生类Girl的访问,降低了系统间的耦合,提高了系统的健壮性。

在实践中经常出现这样一个方法,放在本类中也可以,放到其它类中也可以。那怎么处理呢?你可以坚持一个原则:如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,那就放到本类中。

迪米特原则的核心观念就是类间解耦,弱耦合,只有弱耦合后,类的复用率才可以提高。其结果就是产生了大量的中转或跳转类,导致系统复杂,为维护带来了难度。所以,我们在实践时要反

复权衡,即要让结构清晰,又做到高内聚低耦合。

3、自己理解

迪米特原则在自己开发中一定要培养这种思想,因为它没有那么模糊,而且这个原则没啥争议。

六、开闭原则

这个原则更像是前五个原则的总纲,前五个原则就是围着它转的,只要我们尽量的遵守前五个原则,那么设计出来的系统应该就比较符合开闭原则了,相反,如果你违背了太多,那么你的系统或许也不太遵循开闭原则。

1、定义

一句话,对修改关闭,对扩展开放。

就是说我任何的改变都不需要修改原有的代码,而只需要加入一些新的实现,就可以达到我的目的,这是系统设计的理想境界,但是没有任何一个系统可以做到这一点,哪怕我一直最欣赏的

spring框架也做不到,虽说它的扩展性已经强到变态。这个就不说了,字面上也能理解个八九分,它对我来讲太抽象。虽然它很重要。

关于“java设计模式之六大原则分别是什么”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。


推荐阅读
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
author-avatar
etqq
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有