设计模式(一)

设计模式(一)

  • 七大原则
  1. 单一职责原则
    方法级别的单一职责原则

    就是一个类中,有多个方法去针对不同的对象给出不同的具体实现,也就是一个方法只负责一个具体的功能

    类级别的单一职责原则

    一个类只负责某个功能的模块的实现,并且这个列中的方法也是只针对于这个类对象的,这个在web开发中经常用到的就是MVC三层架构中每一层都分为各个模块,然后再不同的类中给出对应模块的实现方法。

    单一指责原则简单案例(交通工具类的设计)
    结构图:

    image.png

  2. 接口隔离原则

理解:
当一个类去实现某一个接口的时候需要实现接口中的所有方法,这个时候如果实现类只需要使用到的是借口中的部分方法时,其余的方法就很多多余了。所以就需要将接口分成更小的部分,每个接口中只有部分方法,这样一来,实现类就可以通过实现单个或多个接口来实现之前接口中的部分方法,而没有必要去实现接口中所有的方法。
uml类图说明:
不好的实现方式:

image.png

根据接口隔离原则改进
image.png

改进之后的不同之处在于原先接口中的各个方法都是放在一个接口中,通过将原来的接口拆分成几个小的接口,这样一来,A依赖于B中的各个1,2,3方法就只需要通过实现两个就可以实现了。同理对于C依赖于D中的1,4,5方法的话也只需要实现两个 不同的接口就可以直接实现了

  1. 依赖倒转原则
    理解:

细节依赖抽象,高层模块不应依赖于低层模块,并且这二者都应该去依赖于接口,也就是说在传递类作为参数的时候应该尽可能的传递某个类的接口或抽象类,而不是传递一个具体的实现类,即使这个类是比较高层的并且不常常修改的,毕竟谁也不知道这个类什么时候要改。
上面的这些其实核心都是面向接口编程。
举例:设计一个电视机类,并且实现开关电视的功能
代码实现1(接口实现依赖倒转)

package demo.principle.dependencyInverse;

public class DependencyInverse1 {
        public static void main(String[] args) {
            IOpenAndClose io = new HaierOpenClose();
            ITv tv = new Haier();
            io.open(tv);
            io.close(tv);
        }
}

// 方式1, 接口依赖倒转
// 电视机有多个种类,而打开关闭电视机的方式也是有多个种类,所以对于打开关闭需要依赖的是一个电视机接口,同时打开关闭需要
// 针对不同的电视机实现不同的功能,这个地方用到的就是单一指责原则,对于这个打开和关闭的接口,可以使用方法级别的单一职责。
interface ITv{
    void paly();
    void close();
}
// 执行电视机操作的接口
interface IOpenAndClose{
    void open(ITv tv);      // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
    void close(ITv tv);
}

// 具体的电视机实现类
class Haier implements ITv{

    @Override
    public void paly() {
        System.out.println("海尔电视机开始运行");
    }

    @Override
    public void close() {
        System.out.println("海尔电视机关闭");
    }
}
// 开关操作的实现类
class HaierOpenClose implements IOpenAndClose{

        @Override
        public void open(ITv tv) {
            tv.paly();
        }
        
        @Override
        public void close(ITv tv) {
            tv.close();
        }
}

UML类图:

image.png

实现方式2:(构造方法实现依赖倒转)
代码实现:

package demo.principle.dependencyInverse;

public class DependencyInverse2 {
        public static void main(String[] args) {
            // 因为在同一个包下,相同的类和接口会存在冲突,重新这几个类,不过对于实际应用中只需要一个接口即可,几种
            // 依赖倒转方式也只需要一种实现就行。
            IOpenAndClose2 io = new HaierOpenClose2();
            ITv2 tv = new Haier2();
            io.open();
            io.close();
        }
}

// 方式2:构造器实现依赖倒转,和接口依赖倒转不同 的是这里不直接在IOpenAndClose2接口中传递ITv2接口,而是在其实现类中
//      增加一个成员变量为ITv
interface ITv2{
    void paly();
    void close();
}
// 执行电视机操作的接口
interface IOpenAndClose2{
    void open();        // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
    void close();
}

// 具体的电视机实现类
class Haier2 implements ITv2{

    @Override
    public void paly() {
        System.out.println("海尔电视机开始运行");
    }

    @Override
    public void close() {
        System.out.println("海尔电视机关闭");
    }
}

// 开关操作的实现类
class HaierOpenClose2 implements IOpenAndClose2{
        private ITv2 tv;
        // 构造器将tv属性实例化,从而实现对象注入到HaierOpenClose2类中
        public HaierOpenClose2() {
            this.tv = new Haier2();
        }
        @Override
        public void open() {
            this.tv.paly();
        }
        @Override
        public void close() {
            this.tv.close();
        }
        
}

UML类图

image.png

实现方式3:(set方法实现依赖倒转)
代码实现:

package demo.principle.dependencyInverse;

public class DependencyInverse3 {
       public static void main(String[] args) {
           // 因为在同一个包下,相同的类和接口会存在冲突,重新这几个类,不过对于实际应用中只需要一个接口即可,几种
           // 依赖倒转方式也只需要一种实现就行。
           IOpenAndClose3 io = new HaierOpenClose3();
           ITv3 tv = new Haier3();
           io.setTv(tv);
           io.open();
           io.close();
       }
}

// 方式2:set方法实现依赖倒转,这个只需要将构造器方式改一下就行
interface ITv3{
   void paly();
   void close();
}
// 执行电视机操作的接口
interface IOpenAndClose3{
   void open();        // 通过依赖电视机接口去依赖具体的电视机,然后调用电视机功能
   void close();
   void setTv(ITv3 tv);
}

// 具体的电视机实现类
class Haier3 implements ITv3{

   @Override
   public void paly() {
       System.out.println("海尔电视机开始运行");
   }

   @Override
   public void close() {
       System.out.println("海尔电视机关闭");
   }
}

// 开关操作的实现类
class HaierOpenClose3 implements IOpenAndClose3{
       private ITv3 tv;
       
       @Override
       public void setTv(ITv3 tv) {
           this.tv = tv;
       }
       
       @Override
       public void open() {
           this.tv.paly();
       }
       @Override
       public void close() {
           this.tv.close();
       }
       
}

UML类图:

image.png

依赖倒转原则细节:
底层(具体实现那层)尽量要有接口或抽象类,或者两者都有。对象之间的依赖尽可能的通过接口进行依赖而不是直接通过具体实现类进行依赖,这样一来在对象和对象之间就存在一个缓冲层,有利于扩展。

  1. 里氏替换原则(达到所有引用基类的地方都是透明的,也就是说在所有具体实现类使用的时候都能明确的知道他使用的是自己的方法还是父类的方法)
    举例分析:
    代码:
  **不好的方式:**
  package demo.principle.lissubstitution;

public class LsSubstitution {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("a+b = "+a.fun1(4, 5));
        System.out.println("-----------------------");
        B b = new B();
        // 之所以会这么写是因为假设了程序员没有意识到自己重写了父类的方法,所以认为调用的还是父类的方法
        System.out.println("a+b = "+b.fun1(6, 7));
    }
}

class A{
    // 求和
    public int fun1(int a, int b) {
        return a+b;
    }
}

class B extends A{
    // 求差,但是实际上程序员在写这个方法实现上是可能是不会想到自己已经重写了父类的方法(其实好像这个说法就不大对,既然
    // 继承了,又怎么会不知道自己是不是重写了父类的方法呢,但是理解上好像是这么个意思)
    public int fun1(int a, int b){
        return a-b;
    }
}



  **改进的方式:**
    package demo.principle.lissubstitution;

public class LsSubstitution2 {
    public static void main(String[] args) {
        A2 a = new A2();
        System.out.println("a+b = "+a.fun1(4, 5));
        System.out.println("-----------------------");
        B2 b = new B2();
        b.setA(a);
        // 通过去掉B类和 A类的继承关系,这样在使用B类的时候就不会再因为A类是其父类而直接调用父类方法了。
        // 也就很明确了B类使用时候的功能了
        System.out.println("a-b = "+b.fun1(6, 7));
        System.out.println("a+b = "+b.getA().fun1(6, 7));
    }
}

// 构建一个更顶层的base类,从而去掉A类和B类之间的继承关系
class Base{
    
}

class A2 extends Base{
    // 求和
    public int fun1(int a, int b) {
        return a+b;
    }
}

class B2 extends Base{
    // 求差,但是实际上程序员在写这个方法实现上是可能是不会想到自己已经重写了父类的方法(其实好像这个说法就不大对,既然
    // 继承了,又怎么会不知道自己是不是重写了父类的方法呢,但是理解上好像是这么个意思)
    private A2 a;
    
    // 将A和B类聚合
    public void setA(A2 a) {
        this.a = a;
    }
    
    public A2 getA() {
        return a;
    }
    
    public int fun1(int a, int b){
        return a-b;
    }
}


PS: 其实对这个里氏替换原则还是不大理解,怎么可能会出现继承一个类而不知道自己啥时候重写了父类方法的情况。。

5. 开闭原则:
其实这个就是通过接口来使用其子类功能,前边的依赖倒转其实也是使用了开闭原则,还有这学期上课说到的接口使用子类实现求矩形面积也是使用了开闭原则。实际开发的时候再补充吧,感觉真的没什么好写的。

6. 迪米特法则
就是类和类之间的耦合较低,只有直接朋友,尽可能的避免使用间接朋友。
什么是直接朋友呢?一个类的参数,返回值,中使到了其他的类,这些类叫做直接朋友。反之,在方法中突然出现的(new出来的)则是间接朋友。为什么要遵循这个迪米特法则呢,这是为了避免在程序模块的修改是影响到其他功能模块的功能。
案例:设计类输出学校总部和学院的员工信息
代码:

package demo.principle.demite;

import java.util.ArrayList;
import java.util.List;

/**
 * 有一个学校,学校里包括学校总部和各个学院,要求设计输出学校总部的员工id和学院的员工id
 * @author My
 *
 */
public class Dimite {
    public static void main(String[] args) {
        System.out.println("输出学校员工信息如下");
        ScoolHQ scoolHQ = new ScoolHQ();
        new ScoolHQManager().printHQEmploee(scoolHQ);
        System.out.println("================================");
        Colleage colleage = new Colleage();
        new ColleageManager().printEmployee(colleage);
    }
}

//员工
class Employee{
    private String id;
    
    public Employee(String id) {
        this.id = id;
    }
    
    public String getId() {
        return this.id;
    }
}
// 学校总部
class ScoolHQ{
    // 直接朋友,Employee,间接朋友,无
    private List<Employee> list = new ArrayList<>();
    public ScoolHQ() {
        for (int i = 0; i < 10; i++) {
            list.add(new Employee(String.valueOf(i)));
        }
    }
    
    public List<Employee> getEmploee() {
        return this.list;
    }
}

// 
class ScoolHQManager{
    // 直接朋友ScoolHQ,间接朋友,无
    public void printHQEmploee(ScoolHQ hq) {
        List<Employee> list = hq.getEmploee();
        for (int i = 0; i < list.size(); i++) {
            System.out.println("第"+(i+1)+"个员工的id为"+list.get(i).getId());
        }
    }
}
// 学院
class Colleage{
    // 直接朋友 Employee,简介朋友,无
    List<Employee> list = new ArrayList<>();
    public Colleage() {
        for (int i = 0; i < 5; i++) {
            list.add(new Employee("0"+String.valueOf(i)));
        }
    }
    
    public List<Employee> getColleageEmployee() {
        return this.list;
    }
}

class ColleageManager{
    // 直接朋友Colleage, 简介朋友,无
    public void printEmployee(Colleage colleage){
        List<Employee> list = colleage.getColleageEmployee();
        int count = 1;
        for (Employee employee : list) {
            System.out.println("第"+(count++)+"员工的id为"+employee.getId());
        }
    }
    
}

7.合成复用原则

尽量避免只用继承关系,而使用聚合,组合这些关系去代替。

8. uml类图关系
uml类图基本符号:

image.png

8.1 依赖关系

一个类A中用到了其他类B, 类C,也就是一个类B,C对象作为类A的成员变量。 什么是使用到呢?包括这些情况,在类A中的方法参数中用到,返回值中用到, 成员属性,方法内部用到。
类图:

image.png

8.2 泛化(实际上就是继承关系, 依赖关系的特例)

如果A继承了B类,那么AB之间就是继承关系(很简单的实现,不画类图了)

8.2 实现(类实现接口, 也是依赖关系的特例),类图略

8.3 关联关系(依赖关系的特例),类图略,懂得下边的聚合和组合之后这个应该就明白了,和数据库中的关联是差不多的,只是这变成了类之间的关联

类与列之间的一对一,一对多,多对多的关系

8.4 聚合关系(关联关系的特例)

具有导航型和多重性
整体和部分可以分开


image.png

注意,在聚合关系中,主类的中包含的其他类作为成员对象,但是没有在主类创建(即new 一个对象)的时候立即将其成员实例化,这意味着主类创建的时候对于其他类的成员不是必须的(很显然,成员没有实例化为空嘛),这一点是区别于组合关系的。在类图中的体现是主类的成员字段并没有直接跟上一个new(实例化的操作),可以对比着组合关系的类图查看。

8.5 组合关系

整体和部分不可分开
um类图


image.png

在Computer主类构造的时候KeyBoard和MOnitor就被实例化了,也就是说一个电脑在创建的时候就必须要有一个键盘和一个显示屏。

在某些情况下组合关系可能会变成聚合关系,聚合关系也可能变成组合关系,比如说在设计一个人person,和其对应的IDcard在删除的时候就是组合关系,删除一个人的时候需要将其对应的身份证号删除, 而在开始定义的时候人和身份证是聚合关系,一个人不一定要有身份证。

附录:

eclipseUML类图设计环境搭建:
1. 下载GEF:
eclipse->help->install new SoftWare

将`GEF - http://download.eclipse.org/tools/gef/updates/releases/`拷贝到图片中所示位置,或者点击add,分别将名称和连接填入对应的两个输入框。链接前半部分是名称,后半部分是GEF下载地址。
图片.png
  1. 安装amateras插件
    [http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=AmaterasUML](http://amateras.sourceforge.jp/cgi-bin/fswiki_en/wiki.cgi?page=AmaterasUML)下载压缩包,解压后将三个jar包拷贝到eclipse的plugins文件夹下,重启eclipse,然后新建文件时输入class,就会看得到class Diagram, 选择class Diagram就是类图了(其他amateras是其他种类的图,感兴趣自行了解,设计模式主要用的是类图)。

今日内容总结:

基本粗枝大叶的学了边设计模式的七大原则,还有UML类图的基本使用,不过这些案例并不是自己想出来的。一时也想不到什么比较好的例子。好友就是,案例代码时根据自己的理解写的,不敢保证理解的正确性,日后在做项目实战中用到了再回来改吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 哲学思想 天道观 《礼记》的天道观,反映了先秦儒家天道观发展的大背景,其主要内容集中反映在《中庸》、《礼运》、《乐...
    流泪的芍药花阅读 520评论 0 0
  • # 操作系统 ## 第一章 操作系统引论 **1.操作系统的概念** 操作系统是配置在计算机硬件上的第一层软件,是...
    人莽阅读 405评论 0 0
  • 《思考快与慢》读书心得 人性七罪宗是什么?翻开诺奖得主卡尼曼的《思考,快与慢》,深刻到让你恐惧。 历史上第一位获...
    jony520阅读 341评论 0 0
  • 2018年8月7日 星期二 天气晴 上午突然接到老师的电话,让我下午去舞蹈班一趟,说是下午有场体验课,要几个...
    丫丫宝贝0507阅读 422评论 0 0
  • ————————交通环境—————————— 1.位于107街,靠近Broadway,走路到哥伦比亚大学仅需十分钟...
    Lynn_Cai阅读 546评论 0 0