• 设计模式学习总结(一)——设计原则与UML统一建模语言


    目录

    一、概要

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。可复用、可扩展、可维护

    设计模式是GOF(Group Of Four Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides )所著的《设计模式:可复用面向对象软件的基础》一书中所描述的23中经典设计模式,此书奠定了模式在软件行业中的地位,从此人们提到“设计模式”就是默认指“面向对象设计模式”

    1.1、设计模式定义

    每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心
    特定的问题,特定的解决套路

    1.2、设计模式分类

    设计模式分为三大类共23种:

    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    1.3、设计模式书籍

    Head First设计模式(中文版)HEAD FIRST Jolt震撼大奖headfirst设计模式深入浅出讲清 java设计模式计算机编程自学入门

    大话设计模式

    二、UML统一建模语言

     统一建模语言(UML,Unified Modeling Language)是面向对象软件的标准化建模语言。UML因其简单、统一的特点,而且能表达软件设计中的动态和静态信息,目前已成为可视化建模语言的工业标准。在软件无线电系统的开发过程中,统一建模语言可以在整个设计周期中使用,帮助设计者缩短设计时间,减少改进的成本,使软硬件分割最优。

    2.1、UML分类

    UML定义了5类,10种模型图、五种类图定义:
    1.用例图:从用户角度描述系统功能,并指各功能的操作者。
    2.静态图:包括类图,包图,对象图。
    类图:描述系统中类的静态结构
    包图:是包和类组成的,表示包与包之间的关系,包图描述系统的分层结构
    对象图:是类图的实例
    3.行为图:描述系统动态模型和对象组成的交换关系。包括状态图和活动图
    活动图:描述了业务实现用例的工作流程
    状态图:是描述状态到状态控制流,常用于动态特性建模
    4.交互图:描述对象之间的交互关系
    顺序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互
    合作图:描述对象之间的协助关系
    5.实现图:
    配置图:定义系统中软硬件的物理体系结构
      
    十种模型图定义:
    (1)、用例图:展示系统外部的各类执行者与系统提供的各种用例之间的关系
    (2)、类图:展示系统中类的静态结构
    (3)、对象图:是类图的一种实例化图(对象图是对类图的一种实例化)
    (4)、包图:是一种分组机制。在UML1.1版本中,包图不再看作一种独立的模型图)

    2.2、类图

    建模工具:Rose

    COCOMO,英文全称为constructive cost model,中文为构造性成本模型。它是一种精确、易于使用的,基于模型的成本估算方法。

    2.2.1、关联



    双向关联
    C1-C2:指双方都知道对方的存在,都可以调用对方的公共属性和方法。
    在GOF的设计模式书上是这样描述的:虽然在分析阶段这种关系是适用的,但我们觉得它对于描述设计模式内的类关系来说显得太抽象了,因为在设计阶段关联关系必须被映射为对象引用或指针。对象引用本身就是有向的,更适合表达我们所讨论的那种关系。所以这种关系在设计的时候比较少用到,关联一般都是有向的。
    使用ROSE 生成的代码是这样的:

    双向关联在代码的表现为双方都拥有对方的一个指针,当然也可以是引用或者是值。

    单向关联:

    /**学生累*/
    public class Student {
    }
    /**学校类*/
    public class School {
        public Student tom;
    }

    C3->C4:表示相识关系,指C3知道C4,C3可以调用C4的公共属性和方法。没有生命期的依赖。一般是表示为一种引用。
    生成代码如下:

    单向关联的代码就表现为C3有C4的指针,而C4对C3一无所知。



    自身关联(反身关联):
    自己引用自己,带着一个自己的引用。

    代码如下:

    复制代码
    public class Node {
        //数据
        public int data;
        
        public Node next;
        public Node prev;
    }
    复制代码

    就是在自己的内部有着一个自身的引用。

    2.2.2、聚合/组合

    当类之间有整体-部分关系的时候,我们就可以使用组合或者聚合。

    聚合:是一种弱偶合的关联关系。

    /**人*/
    public class Person {
    }
    /**引擎*/
    public class Engine {
    }
    复制代码
    /**车*/
    public class Car {
        /**组合*/
        private Engine engine;
        public Car(Engine engine){
            this.engine=engine;
        }
    
        /**聚合*/
       public Person driver;
    }
    复制代码

    组合(也有人称为包容):一般是实心菱形加实线箭头表示,如上图所示,表示的是C8被C7包容,而且C8不能离开C7而独立存在。但这是视问题域而定的,例如在关心汽车的领域里,轮胎是一定要组合在汽车类中的,因为它离开了汽车就没有意义了。但是在卖轮胎的店铺业务里,就算轮胎离开了汽车,它也是有意义的,这就可以用聚合了。在《敏捷开发》中还说到,A组合B,则A需要知道B的生存周期,即可能A负责生成或者释放B,或者A通过某种途径知道B的生成和释放。

    2.2.3、依赖



    依赖是一种非常弱的关联关系。
    复制代码
    /**人*/
    public class Person {
        /**吃食物*/
        public void eat(Food food){
            System.out.println("正在吃"+food.name);
        }
    }
    复制代码

    那依赖和聚合组合、关联等有什么不同呢?
    关联是类之间的一种关系,例如老师教学生,老公和老婆,水壶装水等就是一种关系。这种关系是非常明显的,在问题领域中通过分析直接就能得出。
    依赖是一种弱关联,只要一个类用到另一个类,但是和另一个类的关系不是太明显的时候(可以说是“uses”了那个类),就可以把这种关系看成是依赖,依赖也可说是一种偶然的关系,而不是必然的关系,就是“我在某个方法中偶然用到了它,但在现实中我和它并没多大关系”。例如我和锤子,我和锤子本来是没关系的,但在有一次要钉钉子的时候,我用到了它,这就是一种依赖,依赖锤子完成钉钉子这件事情。

    组合是一种整体-部分的关系,在问题域中这种关系很明显,直接分析就可以得出的。例如轮胎是车的一部分,树叶是树的一部分,手脚是身体的一部分这种的关系,非常明显的整体-部分关系。
    上述的几种关系(关联、聚合/组合、依赖)在代码中可能以指针、引用、值等的方式在另一个类中出现,不拘于形式,但在逻辑上他们就有以上的区别。
    这里还要说明一下,所谓的这些关系只是在某个问题域才有效,离开了这个问题域,可能这些关系就不成立了,例如可能在某个问题域中,我是一个木匠,需要拿着锤子去干活,可能整个问题的描述就是我拿着锤子怎么钉桌子,钉椅子,钉柜子;既然整个问题就是描述这个,我和锤子就不仅是偶然的依赖关系了,我和锤子的关系变得非常的紧密,可能就上升为组合关系(让我突然想起武侠小说的剑不离身,剑亡人亡...)。这个例子可能有点荒谬,但也是为了说明一个道理,就是关系和类一样,它们都是在一个问题领域中才成立的,离开了这个问题域,他们可能就不复存在了。

    2.2.4、泛化(继承)



    泛化关系:如果两个类存在泛化的关系时就使用,例如父和子,动物和老虎,植物和花等。
    ROSE生成的代码很简单,如下:

    /**学生累*/
    public class Student extends Person {
    }
    在UML建模中,对类图上出现元素的理解是至关重要的。开发者必须理解如何将类图上出现的元素转换到Java中。以java为代表结合网上的一些实例,下面是个人一些基本收集与总结:
     
    基本元素符号:
     
    1. 类(Classes)
    类包含3个组成部分。第一个是Java中定义的类名。第二个是属性(attributes)。第三个是该类提供的方法。
    属性和操作之前可附加一个可见性修饰符。加号(+)表示具有公共可见性。减号(-)表示私有可见性。#号表示受保护的可见性。省略这些修饰符表示具有package(包)级别的可见性。如果属性或操作具有下划线,表明它是静态的。在操作中,可同时列出它接受的参数,以及返回类型,如下图所示:

     
    2. 包(Package)
    包是一种常规用途的组合机制。UML中的一个包直接对应于Java中的一个包。在Java中,一个包可能含有其他包、类或者同时含有这两者。进行建模时,你通常拥有逻辑性的包,它主要用于对你的模型进行组织。你还会拥有物理性的包,它直接转换成系统中的Java包。每个包的名称对这个包进行了惟一性的标识。

    3. 接口(Interface)
    接口是一系列操作的集合,它指定了一个类所提供的服务。它直接对应于Java中的一个接口类型。接口既可用下面的那个图标来表示(上面一个圆圈符号,圆圈符号下面是接口名,中间是直线,直线下面是方法名),也可由附加了<<interface>>的一个标准类来表示。通常,根据接口在类图上的样子,就能知道与其他类的关系。

    关系:
     
    1. 依赖(Dependency)
    实体之间一个“使用”关系暗示一个实体的规范发生变化后,可能影响依赖于它的其他实例。更具体地说,它可转换为对不在实例作用域内的一个类或对象的任何类型的引用。其中包括一个局部变量,对通过方法调用而获得的一个对象的引用(如下例所示),或者对一个类的静态方法的引用(同时不存在那个类的一个实例)。也可利用“依赖”来表示包和包之间的关系。由于包中含有类,所以你可根据那些包中的各个类之间的关系,表示出包和包的关系。

    2. 关联(Association)
    实体之间的一个结构化关系表明对象是相互连接的。箭头是可选的,它用于指定导航能力。如果没有箭头,暗示是一种双向的导航能力。在Java中,关联转换为一个实例作用域的变量,就像图E的“Java”区域所展示的代码那样。可为一个关联附加其他修饰符。多重性(Multiplicity)修饰符暗示着实例之间的关系。在示范代码中,Employee可以有0个或更多的TimeCard对象。但是,每个TimeCard只从属于单独一个Employee。

     
     
    3. 聚合(Aggregation)
    聚合是关联的一种形式,代表两个类之间的整体/局部关系。聚合暗示着整体在概念上处于比局部更高的一个级别,而关联暗示两个类在概念上位于相同的级别。聚合也转换成Java中的一个实例作用域变量。
    关联和聚合的区别纯粹是概念上的,而且严格反映在语义上。聚合还暗示着实例图中不存在回路。换言之,只能是一种单向关系。
     

    4. 组合(Composition)
    合成是聚合的一种特殊形式,暗示“局部”在“整体”内部的生存期职责。合成也是非共享的。所以,虽然局部不一定要随整体的销毁而被销毁,但整体要么负责保持局部的存活状态,要么负责将其销毁。
    局部不可与其他整体共享。但是,整体可将所有权转交给另一个对象,后者随即将承担生存期职责。Employee和TimeCard的关系或许更适合表示成“合成”,而不是表示成“关联”。

    5. 泛化(Generalization)
    泛化表示一个更泛化的元素和一个更具体的元素之间的关系。泛化是用于对继承进行建模的UML元素。在Java中,用extends关键字来直接表示这种关系。

     
    6. 实现(Realization)
    实例关系指定两个实体之间的一个合同。换言之,一个实体定义一个合同,而另一个实体保证履行该合同。对Java应用程序进行建模时,实现关系可直接用implements关键字来表示。

    引用地址:http://www.cnblogs.com/riky/archive/2007/04/07/704298.html

    三、设计原则

    2.1、单一职责原则(SRP)

    一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。

    一个类只担任一种角色。

     下面这个jsp页面就不符合SRP原则,它要担任展示UI与访问数据库两个角色。分层是一种解决办法。

    复制代码
    <%@ page contentType="text/html; charset=gb2312" %>   
    <%@ page language="java" %>   
    <%@ page import="com.mysql.jdbc.Driver" %>   
    <%@ page import="java.sql.*" %>   
    <%   
    //加载驱动程序   
    String driverName="com.mysql.jdbc.Driver";   
    //数据库信息  
    String userName="root";   
    //密码   
    String userPasswd="123";   
    //数据库名   
    String dbName="Student";   
    //表名   
    String tableName="stu_info";   
    //将数据库信息字符串连接成为一个完整的url(也可以直接写成url,分开写是明了可维护性强)   
      
    String url="jdbc:mysql://localhost/"+dbName+"?user="+userName+"&password="+userPasswd;   
    Class.forName("com.mysql.jdbc.Driver").newInstance();   
    Connection conn=DriverManager.getConnection(url);   
    Statement stmt = conn.createStatement();   
    String sql="SELECT * FROM "+tableName;   
    ResultSet rs = stmt.executeQuery(sql);   
    out.print("id");   
    out.print("|");   
    out.print("name");   
    out.print("|");   
    out.print("phone");   
    out.print("<br>");   
    while(rs.next()) {   
    out.print(rs.getString(1)+" ");   
    out.print("|");   
    out.print(rs.getString(2)+" ");   
    out.print("|");   
    out.print(rs.getString(3));   
    out.print("<br>");   
    }   
    out.print("<br>");   
    out.print("ok, Database Query Successd!");   
    rs.close();   
    stmt.close();   
    conn.close();   
    %>  
    复制代码

    2.2、开闭原则(Open Close Principle OCP)

    开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

    美猴王说:"'皇帝轮流做,明年到我家。'只教他搬出去,将天宫让于我!"对于这项挑战,太白金星给玉皇大帝提出的建议是:"降一道招安圣旨,宣上界来…,一则不劳师动众,二则收仙有道也。

    不要随意修改顶层接口,可以通过继承或其它办法扩展出新的内容。

    2.3、里氏代换原则(Liskov Substitution Principle LSP)

    里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。老鼠的儿子会打洞。

            Person tom=new Student();
            Object mark=new Teacher();

    2.4、依赖倒转原则(Dependence Inversion Principle DIP)

    所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
    实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。 细节应该依赖抽象,抽象应该依赖抽象,抽象不应该依赖细节

    复制代码
    /**人*/
    public class Person {
        /**吃食物*/
        public void eat(Food food){  //依赖具体
            System.out.println("正在吃"+food.name);
            Person tom=new Student();
            Object mark=new Teacher();
        }
    
        //Student继承Person
        //依赖抽象
        public void Show(Person person){
        }
        //依赖具体
        public void Hello(Student student){
        }
    }
    复制代码

    2.5、接口隔离原则(Interface Segregation Principle ISP)

    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

    复制代码
    /**可飞的*/
    interface IFlyable{
        public void fly();
    }
    
    /**下蛋*/
    interface  ILayeggAble{
        public void Layegg();
    }
    
    interface  IBirdable{
        public void fly();
        public void Layegg();
    }
    复制代码

    IBirdable如果被超人(SuperMan)实现则除了要实现fly方法飞还要实现下蛋接方法,超人下蛋不太合适。

    2.6、合成复用原则(Composite Reuse Principle)

    合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。

    2.7、迪米特法则(最少知道原则)(Demeter Principle)

    为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。也就是说一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易,这是对软件实体之间通信的限制,它要求限制软件实体之间通信的宽度和深度。

    四、示例与资料

    示例:https://coding.net/u/zhangguo5/p/DP01/git

    视频:https://www.bilibili.com/video/av15867320/

    五、视频与作业

    5.1、作业

    1、使用java代码实现下图,理解他们之间的关系

    2、请记住类之间的关系

    3、请记住5大设计原则并作简单描述

  • 相关阅读:
    :Spring + axis2 开发 webservice
    log file parallel write 和 log buffer space p1 p2 p3
    log file sync p1 p2 p3
    :Apache FTPClient操作“卡死”问题的分析和解决
    :Apache FTPClient操作“卡死”问题的分析和解决
    org.apache.catalina.LifecycleException: Failed to start component
    一个简单的Tk界面(可以录入和查询)
    函数调用子函数,注意子函数的位置
    Perl 采集磁盘信息
    Perl 使用Frame(放置其他控件的地方)
  • 原文地址:https://www.cnblogs.com/qq895139140/p/7770587.html
Copyright © 2020-2023  润新知