胡侃软件之设计原则

2024-02-05 19:40
文章标签 设计 软件 原则 胡侃

本文主要是介绍胡侃软件之设计原则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1. 概述

2. 设计原则

2.1. 职责单一原则

2.2. 里氏替换原则

2.3. 依赖倒转原则

2.4. 接口隔离原则

2.5. 迪米特原则

2.6. 开闭原则

 

1 概述

        软件设计是从需求规格说明书开始,根据需求分析阶段确定的功能设计软件系统的整体结构、划分功能模块、确定每个模块的实现算法以及编写具体的代码,形成软件最终的软件产品.软件设计是把许多事物和问题抽象出来。将问题或事物分解并且模块化使得解决问题变得容易,同时分解的越细模块数量也就越多,这带来的副作用就是使得设计者需要考虑更多的模块之间耦合度的情况。

软件设计包括软件的结构设计,数据设计,接口设计和过程设计.软件是对真实世界的抽象,是对某种思想的总结和呈现.思想和对真实世界的看法是不断变化的,因此软件是会不断变化的.软件的设计为了让这些变化变得更加容易,成本更低而总结的一套方法.

2 设计原则

        软件设计的原则主要是解耦各个模块之间的关系,这里需要明确一点,软件设计出来是给人看的,解耦是指软件在开发阶段的时候对于其他模块的依赖,不是在运行阶段彻底分离.如果在运行阶段也彻底分离那就说明这个模块在此系统中没有任何作用可以去掉了.

一个优秀的设计可以应对软件的变化,可以方便的对现有软件进行扩展,可以很容易的对现有程序进行修改,可以很容易理解软件各个模块之间的关系.将程序修改成本降到最低.为此业界各个大神总结了一套软件设计的原则.

 2.1职责单一原则

      只因为一种需求变化引起本模块的修改.如果有多重需求的变化都会引起此模块的变化,那么就应该考虑将模块进行分离,重新设计.职责单一原则的好处1,降低类的复杂度,实现什么功能很明确.2,提高可读性.3,提高可维护性,因为复杂度降低和可读性提高可维护性自然就上来了.4,变更引起的风险降低,这是单一原则最大的好处.一个接口修改只对相应的实现类有影响,与其他的接口无影响,这个是对项目有非常大的帮助.

我们看一个例子,现在手机都有拍照和播放音乐的功能,我们程序模拟手机这两功能进行设计.类图如下:

这个设计看起来没有什么问题,功能能够很好的完成.那么问题来了拍照和播放音乐在系统中有直接关系吗?很明显没有.那就说明,Mobile类的修改会有两种不同的需求变化引起.这样设计就不符合职责单一原则.修改如下:

这样修改之后手机只关心功能的接口,不在关心具体的实现,接口实现只针对具体的摸一个功能来完成.而且只有一种变化会引起实现类的修改.

职责单一原则也是比较有争议的原则,职责的定义很难有一个明确的标准.因此在项目中很难看到单一原则的影子,在实际的项目环境中,由于开发工作量大,人员技术水平以及项目紧迫程度都会导致大量的设计违背这一原则.而且原本一个类可以实现的功能可能会被拆分到多个类中实现,然后在采用聚合等方式合并到一起.这样也增加了开发工作量.所以导致了很多人放弃了此原则.

职责单一原则不仅仅是在接口和类中,同样也实用于方法.一个方法应该尽可能只完成一件单一的事情.俗话说,我简单我快乐就是这样的.比如某些方法喜欢根据参数来进行switch case然后做不同的事情,这样代码其实是非常糟糕的代码.一般的做法是将具体的case中的内容单独提取成为一个方法,然后进行调用.有判断的地方可以考虑策略等模式进行处理.

 

2.2里氏替换原则

      基类可以出现的地方都可以用子类进行替换,而不会引起任何不适应的问题.里氏替换原则是继承复用的基石,只有当派生类可以替换掉基类,软件的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为.里氏代换原则是对“开-闭”原则的补充.实现“开-闭”原则的关键步骤就是抽象化.覆盖或实现父类的方法时,子类返回值类型可以是父类返回值类型的子类.

举个例子:现在流行的log4j日志系统,就可有很多种输出方式,可以发送邮件,可以写数据库,可以写文本.其简单设计模型如下:

客户端WriteLog类调用只依赖于抽象类Log4j.同时对于子类实例的创建,这里有很多种方式,比如工厂方法,IoC注入等等,当然也可以在Client类中直接创建,但这种方式不是很好.本例中采用的是属性注入的方式.具体代码如下:

public class Log4jDemo {

    public static void main(String[] args) {
        WriteLog writeLog =new WriteLog(new WriteTextLog());
        writeLog.writeLog("日志");
    }
}

class WriteLog
{
    private Log4j log4j;

    public WriteLog(Log4j log4j)
    {
        this.log4j=log4j;
    }

    public void writeLog(String log)
    {
        log4j.writeLog(log);
    }

}

abstract class Log4j
{
    public abstract void writeLog(String logInfo);
}

class WriteDatabaseLog extends Log4j
{
    private String connectString;

    public String getConnectString() {
        return connectString;
    }

    public WriteDatabaseLog setConnectString(String connectString) {
        this.connectString = connectString;
        return this;
    }

    @Override
    public void writeLog(String logInfo) {
        System.out.println("写数据库日志");
        System.out.println(logInfo);
    }
}

class WriteTextLog extends Log4j
{
    private String filePath;

    public String getFilePath() {
        return filePath;
    }

    public WriteTextLog setFilePath(String filePath) {
        this.filePath = filePath;
        return this;
    }

    @Override
    public void writeLog(String logInfo) {
        System.out.println("写文本日志");
        System.out.println(logInfo);
    }
}

class  SendToEmail extends Log4j
{
    private String emailAddr;

    public String getEmailAddr() {
        return emailAddr;
    }

    public SendToEmail setEmailAddr(String emailAddr) {
        this.emailAddr = emailAddr;
        return this;
    }

    @Override
    public void writeLog(String logInfo) {
        System.out.println("发送邮件");
        System.out.println(logInfo);
    }
}

在本例中WriteLog类中申明了Log4j抽象类,但具体依赖的对象是通过注入方式进来的.注意这里建议只使用抽象类或者接口来做.否则就回违背其他原则(比如依赖倒转原则),而且在WriteLog类中注入的类是Log4j的子类中的任意一个均可以.

里氏替换原则只能正向使用,在这里其实很好理解,子类可以有自己的独有的外观和行为.上面的例子中父类并没有定义自己的外观,仅仅定义了一个行为.每个子类中都有自己的一个独有外观.所以这里大家也应该能够看明白,子类与子类之间并没有直接联系,因此子类和子类直接无法直接替换.这也是在定义接口时必须使用父类或者接口的原因.

在里氏替换原则中还有一个问题需要考虑,那就是子类是否完整整覆盖了父类业务.如果我们子类是对日志进行分析统计,那么这个时候写日志其实就不合适了.

我们下面我们将上面的例子修改一下增加一个日志分析类,类图下:

增加的类用于分析日志中错误出现的次数,代码如下:

class AnalysisLog extends Log4j
{
    @Override
    public void writeLog(String logInfo) {
        int count = logInfo.indexOf("错误");
        System.out.println("有" + count + "条错误");
    }
}

首先可以肯定这个代码是可以正常运行的,也能够得到正确的结果,但是要明白抽象类定义writeLog的方法是用于记录日志,而不是分析统计日志.因此这里的实现放在writeLog中并不合适.那么这就说明AnalysisLog类继承的Log4j类在这里并没有将业务完整覆盖.对于这种问题最简单的处理方式就是直接脱离原来的继承关系,但是脱离之后AnalysisLog类分析日志的时候还是需要日志信息,那么怎么办呢?可以增加一个抽象类,来完成日志信息获取.修改后的类图如下:

修改后的类图是在Analysis与Log4j之间增加了一个抽象类AbsAnalysisLog,将接收日志信息的功能委托出来,然后AnalysisLog和其他的写日志的类各自发展.

继承关系有一个需要注意的是:子类在继承父类之后子类的前置条件应该比父类更加宽松,举个例子:


class Father
{
    public Log4j factory(Log4j log4j)
    {
        System.out.println("Execute father");
        return log4j;
    }

}
class Son extends Father
{
    public Log4j factory(SendToEmail sendToEmail)
    {
        System.out.println("Execute son");
        return sendToEmail;
    }
}
class  main
{
    public static void main(String[] args) {
        Father f=new Son();
        f.factory(new SendToEmail());
        System.out.println("====");
        Son s=new Son();
        s.factory(new SendToEmail());
    }
}

输出结果如下:

这里就说明子类在参数方面缩小了父类的参数范围,这种上面的代码在实际的项目中可能会引起很多不需要麻烦.在main函数中的申明是一个Father类指向一个Son的对象.正常情况应该代用Son的factory方法.但这里执行了Father的方法,因此可能引起意想不到的Bug.所以子类中方法的前置条件必须与父类中被覆盖的方法的前置条件相同或者更宽松.上述例子中的代码其实子类和父类的的factory方面没有关系了.按照大多数的设计应该是子类方法中的参数和父类中的方法参数一样或者是父类方法中参数类型的父类.

子类在覆盖父类方法的时候输出结果可以缩小.意思就是方法返回值可以是父类方法返回值的子类.例子:

class Son2 extends Father
{
    @Override
    public SendToEmail factory(Log4j log4j)
    {
        System.out.println("execute son2");
        return (SendToEmail)log4j;
    }

}

class  Main
{
    public static void main(String[] args) {
        Father f2=new Son2();
        f2.factory(new SendToEmail());
    }
}

 

执行结果如下:

 

里氏替换原则是为了提高程序的健壮性和兼容性.在做版本升级的时候可以有更多的选择考虑上下级兼容.增加了子类扩展了功能可以保证原来的功能不受影响.

2.3依赖倒转原则

      依赖倒转原则简单来说就是高层模块不应该依赖底层模块,抽象不应该依赖细节,细节应该依赖抽象.依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多.以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多.在实际的开发中抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成.我们任然用上一节中的例子来表示类图如下:

在这里对于写日志WriteLog类来说只需要依赖于Log4j这个接口即可.根本不关心具体怎么写日志.给Log4j的任何一种实现都可以完成WriteLog中所需要的功能.

依赖倒置原则的核心思想是面向接口编程,只对契约负责.我们在举一个例子.很多人找对象都有一堆要求.比如会做饭,声音甜美,皮肤白皙,身材苗条,懂的什么叫做爱.好吧这些其实就是一个契约.符合这个契约的女人很多,给他随便哪一个都可以(不允许一夫多妻,要不然可以来多个).下面是类图:

从上面的类图中可以看到这个Man其实很可怜的,最多只有两个符合他的要求(只有两个类实现契约接口),可选余地很少.对于具体是Girl1还是Girl2对于都符合Requirement的要求.给他Girl1还是Girl2都无所谓.具体实现代码如下:

class Main1
{
    public static void main(String[] args) {
        Man zhangsan=new Man();
        Requirement xiaohua=new Girl1();
        xiaohua.setGirlName("小花");
        zhangsan.setRequirementGril(xiaohua);
        zhangsan.marray();
    }
}

class Man
{
    private  Requirement requirementGril;

    public Man setRequirementGril(Requirement requirementGril) {
        this.requirementGril = requirementGril;
        return this;
    }

    public void marray()
    {
        System.out.println("女人"+this.requirementGril.getGirlName()
                           +"有白皙皮肤:" + this.requirementGril.fairComplexion());
          System.out.println("女人"+this.requirementGril.getGirlName()
                           +"有苗条身材:" + this.requirementGril.slimBody());
          System.out.println("女人"+this.requirementGril.getGirlName()
                           +"有甜美声音:" + this.requirementGril.sweetVoice());
          System.out.println("男人与" + requirementGril.getGirlName() +" 结婚");
          this.requirementGril.doCook();
          this.requirementGril.knownLove();
    }
}

interface Requirement {

     String getGirlName();

     void setGirlName(String girlName);

     boolean sweetVoice();

     boolean fairComplexion();

     boolean slimBody();

     void doCook();

     void knownLove();

}

abstract class AbsGirl implements Requirement
{
    private  String girlName;
    @Override
    public String getGirlName() {
        return girlName;
    }
    @Override
    public void setGirlName(String girlName) {
        this.girlName = girlName;
    }


    @Override
    public boolean sweetVoice() {
        return true;
    }

    @Override
    public boolean fairComplexion() {
        return true;
    }

    @Override
    public boolean slimBody() {
        return true;
    }
}

class Girl1 extends AbsGirl
{
    @Override
    public void doCook() {
        System.out.println(getGirlName()+" 会做饭会炒菜");
    }

    @Override
    public void knownLove() {
        System.out.println(getGirlName()+" 懂什么叫做爱");
    }
}

class Girl2 extends AbsGirl
{
    @Override
    public void doCook() {
        System.out.println(getGirlName()+" 会川菜");
    }

    @Override
    public void knownLove() {
        System.out.println(getGirlName()+"知道什么叫做爱");
    }
}

在使用了依赖倒转原则后,如果这个男人想换一个女人结婚,那么很简单,只需在Main1的函数中重新new一个对象即可,修改的范围控制在很小的范围.在实际的开发中这里还可以采用IoC进行注入.将具体的对象的创建放到容器中完成.

实际项目中的重要策略,决定及业务模型正是在这些高层的模块中.也正是这些模型包含着应用的特性.但是,当这些模块依赖于低层模块时,低层模块的修改将会直接影响到高层模块,迫使高层模块也去改变.这种改变是不应该出现.应该是处于高层的模块改变去迫使那些低层的模块发生改变.应该是处于高层的模块优先于低层的模块,改变应该自上而下.无论如何高层的模块也不应依赖于低层的模块.而且,我们想能够复用的是高层的模块.通过类库的形式,我们已经可以复用低层的模块了.当高层的模块依赖于低层的模块时,这些高层模块就很难在不同的环境中复用.但是,当那些高层模块独立于低层模块时,它们就能很简单地被复用了.依赖倒转原则是框架设计中的最核心之处的原则。

 

2.4 接口隔离原则

       接口隔离原则是说应用程序不应该依赖它不需用的接口,类间的依赖关系应该建立在最小的接口上.这个原则有两层第一种解释:是不应该依赖它不需要接口,那依赖什么?依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性;第二中解释:类间的依赖关系应该建立在最小的接口上,它要求是最小的接口,也是要求接口细化,接口纯洁,与第一中解释其实是一样,只是一个事物的两种不同描述.其实总结一下这个原则就一个意思:不要建立大而全的接口.建立多个小接口,小到可能某些接口只有一个方法.这里是不是有点疑惑?和单一职责是不是有点类似?其实这个是有区别的,单一职责强调的职责单一,并没有说接口单一.一个职责可能由多个接口来完成.按照单一职责原则,可以在一个接口中定义所有完成本职责所需的接口,但按照接口隔离的原则则不允许这样定义.我们来一个例子说明.还是接着上面的例子来说明吧.男人想结婚要找对象,因此提出了一堆要求.这些要求一个女人有时候很难满足,因此小三开始登场.好吧我们来分解一下,将一个大而全的要求分解为多个小的要求.为此男人付出的代价就是需要应付多个小三.类图如下:

具体代码如下:


interface IName
{
    String getName();
}

interface IDoCook extends IName
{
    void doCook();
}
interface IKnownLove extends IName
{
    void knownLove();
}
class Gril3 implements IDoCook
{
    private String name;
    public Gril3(String name)
    {
        this.name=name;
    }
    
    @Override
    public void doCook() {
        System.out.println(this.name + " 会做饭,会抓住男人的胃,但不懂什么叫做爱");
    }

    @Override
    public String getName() {
        return name;
    }
}
class Gril4 implements IKnownLove
{

    private String name;
    public Gril4(String name)
    {
        this.name=name;
    }
    @Override
    public void knownLove() {
        System.out.println(this.name+"非常明白什么叫做爱,但不会做饭");
    }

    @Override
    public String getName() {
        return name;
    }
}

class BadMan
{
    private IKnownLove xiaosan;

    private IDoCook wife;

    public BadMan setXiaosan(IKnownLove xiaosan) {
        this.xiaosan = xiaosan;
        return this;
    }

    public BadMan setWife(IDoCook wife) {
        this.wife = wife;
        return this;
    }

    public void marray()
    {
        System.out.println("男人与"+wife.getName()+ " 结婚" );
        wife.doCook();
    }
    public void thirdGird()
    {
        System.out.println("男人找小三" + xiaosan.getName() );
        xiaosan.knownLove();
    }
}

class Main3{
    public static void main(String[] args) {
        IDoCook wife=new Gril3("貂蝉");
        IKnownLove xiaosan=new Gril4("杨玉环");
        BadMan badMan=new BadMan();
        badMan.setWife(wife);
        badMan.setXiaosan(xiaosan);
        badMan.marray();
        badMan.thirdGird();
    }
}

从上面的代码中男人可以完成结婚和找小三的功能.但是男人的欲望在不断膨胀.现在只需要懂什么叫做爱就可以是小三,现在男人增加了一种爱好,比如讨人喜欢.如果按照依赖倒转中提到的例子来做那么你就会发现我们接口已经固定.需要修改接口方能实现.但是如果按照本节中的定义方式来做却只需要增加接口即可完成.

接口隔离核心思想就是接口要足够的小,但是小也不是无限度的小,应该首先满足职责单一原则.接口隔离要求接口足够的小,但我们开发要求接口要高内聚.首先说一下什么是高内聚,高内聚的要求是提高接口,类或者模块的处理能力以减少对外依赖.举个简单例子,战场上司令给军长下达占领A这座城市.然后军长毫不犹豫的回答”是,保证完成任务”.然后军长自己带着部队攻打A这座城市.并且成功占领.这个就是高内聚的表现.在程序中接口隔离原则就是减少能够让外部访问的方法.外部能够访问的方法就表明此类或者接口对外的一种承诺.而这种承诺越少责任就越小,因外部原因而改变的可能性也就越小,因为功能少.

接口设计是有限度的.接口的设计粒度是越小系统越灵活,这是大家都知道的,但副作用是结构的复杂化,开发难度增加,维护性降低,这不是我们所期望看到的,所有接口设计一定要注意适度,适度的“度”怎么来判断的呢?这个度目前没有具体的标准,大部分都是根据经验和常识判断!

接口隔离原则是对接口的定义也同时是对类的定义,接口和类都尽量使用原子接口或原子类来组装,但是这个原子该怎么划分也是设计中的一大难题,在实践中应用时可以根据以下几个规则来衡量:

  •   一个接口只服务于一个子模块或者业务逻辑

通过业务逻辑压缩接口中的 public 方法。接口时常去回顾,尽量做让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法.对于已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理。

  •   了解环境,拒绝盲从

每个项目或产品都有特定的环境因素,别看到别人是这样做的你就照抄,千万别这样,环境不同的,接口拆分的标准就不同.深入了解的业务逻辑,才能设计出合理的接口.

接口隔离原则和其他的设计原则都一样需要花费很多精力来设计,需要从多方面考虑,既要保证灵活性也要保证开发的维护方便性,同时还要能够应对用户提出各种”无理”要求的时候程序能够从容面对.

 

2.5 迪米特原则

        迪米特原则又叫知道最少原则.一个类应该对其他类有最少的了解,通俗的讲一个类对自己需要耦合或者调用的类应该知道的最少,你类内部是怎么复杂,怎么的纠缠不清都和我没关系,那是你的类内部的事情,我就知道你提供的这些接口,然后就调用这些接口.迪米特原则另一解释很有意思:只和直接的朋友交流.我们程序中会有很多的对象,每个对象之间必然会有多种关系,这些关系将对象与对象耦合在一起.只和朋友交流意思就是只与最近的类保持通信.类中又很多种关系组合,聚合,继承,关联,依赖,实现等等.业界将出现在成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友.

举个例子说明吧:

在这个类图中,看起来其实有些别扭.公司知道雇员,这个其实就是违反迪米特法则.我们直接上代码:

class Main4{

    public static void main(String[] args) {

        Company company=new Company();
        for (int i = 0; i < 5; i++) {
            Department department=new Department();
            department.setDepartName("部门" + i);
            for (int j = 0; j < 5; j++) {
                Employee employee=new Employee();
                employee.setName("姓名"+i);
                employee.setNumber("Num:" +i);
                department.addEmployee(employee);
            }
            company.addDept(department);
        }
        company.show();
    }
}

class Company
{
    private String companyName;

    private List<Department> departments=new ArrayList<>();

    public String getCompanyName() {
        return companyName;
    }

    public Company setCompanyName(String companyName) {
        this.companyName = companyName;
        return this;
    }

    public void addDept(Department department)
    {
        departments.add(department);
    }
    public void show()
    {
        this.departments.forEach(x->{
            System.out.println(x.getDepartName());
            x.getEmployees().forEach(e->{
                System.out.println(e.getName()+"," +e.getNumber());
            });
        });
    }


}
class Employee
{
    private String name;
    private String number;

    public String getName() {
        return name;
    }

    public Employee setName(String name) {
        this.name = name;
        return this;
    }

    public String getNumber() {
        return number;
    }

    public Employee setNumber(String number) {
        this.number = number;
        return this;
    }
}

class Department
{

    private String DepartName;

    private List<Employee> employees=new ArrayList<>(16);

    public void addEmployee(Employee employee)
    {
        employees.add(employee);
    }
    public List<Employee> getEmployees()
    {
        return employees;
    }

    public String getDepartName() {
        return DepartName;
    }

    public Department setDepartName(String departName) {
        DepartName = departName;
        return this;
    }

    public void showEmployee()
    {
        employees.forEach(x-> System.out.println(x.getName()
                                                 +","
                                                 + x.getNumber()));
    }
}

这里违反迪米特法则的原因在于Company这个类中出现了Employee类的对象.正常情况是公司只应该有部门,雇员属于部门,那么雇员的信息就应该由部门管理.因此我们修改一下:

class Company
{
    private String companyName;

    private List<Department> departments=new ArrayList<>();

    public String getCompanyName() {
        return companyName;
    }

    public Company setCompanyName(String companyName) {
        this.companyName = companyName;
        return this;
    }

    public void addDept(Department department)
    {
        departments.add(department);
    }
    public void show()
    {
        this.departments.forEach(x->{
            System.out.println(x.getDepartName());
                x.showEmployee();
        });
    }
}

注意黄色部分的代码.其他部分的代码都不变.这样就符合迪米特法则,公司只负责管理部门,不管理雇员,部门负责管理雇员,那就让部门来负责.这样既减少了公司对于雇员的依赖也让职责更加清晰.

朋友之间也应该有距离.比如一个类中需要调用一个陌生类中的方法,但这个陌生是朋友类中可以访问的类,那么应该让朋友类来负责调用.这个听起来有点拗口.举个例子吧比如一般大一点的公司老板不会认识全部的雇员,但是他要下达一个命令给某个他不知道姓名的雇员,那么怎么办呢?找这个雇员所在的部门的负责人来传达.也就是常见的中介模式.例子如下:

具体代码如下:


class Worker{
    private String name;
    public Worker(String name) {
        this.name=name;
    }

    public void doWork()
    {
        System.out.println(name +" execute");
    }

}

class Leader {
    private Worker worker;

    public Leader(Worker worker) {
        this.worker = worker;
    }

    public void command()
    {
        System.out.println("leader 通知工作者");
        worker.doWork();
    }
}

class Boos{
    private Leader leader;

    public Boos(Leader leader) {
        this.leader = leader;
    }

    public void command()
    {
        System.out.println("boss 下命令");
        leader.command();
    }
}
class Main5{
    public static void main(String[] args) {
        Boos boos =new Boos(new Leader(new Worker("张三")));
        boos.command();
    }
}

 

迪米特法则使用需要多方面的考虑,容易在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关.遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联.但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调.在项目常常出现这种情况,一个方法放在本类中也可以,放在其他类中看起来也没有什么不妥,那么究竟放在哪个类中呢?有一个简单的方法可以帮忙辨别,如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,就放置在本类中.上面公司的例子中,如果将打印雇员的信息放到公司中那么就增加了公司对雇员的关系,因此是不合适的.

 

2.6开闭原则

      这个原则是软件设计中的一个基础原则.开闭原则简单来说就是对扩展开放对修改关闭.什么意思呢,其实很简单,就是你可以通过我扩展,但想修改我没门儿.一个类的修改应该只因为错误而修改(需求变更也属于错误).其他任何情况都不应该修改此类,应该通过扩展的方式来实现.

      开闭原则有两种方式一种是多态开闭原则:由于抽象化接口的使用,在这中间实现可以被改变,多种实现可以被创建,并且多态化的替换不同的实现.另一种叫做梅耶开闭原则,提倡实现继承.具体实现可以通过继承方式来重用,但是接口规格不必如此.已存在的实现对于修改是封闭的,但是新的实现不必实现原有的接口.这个原则相对于其他原则遵守的比较好,同时也使用的比较多.其实说简单点就是结合继承和多态的方式来实现类的扩展,而不是去修改原有的类的代码.这个原则是一个基础原则,如果我们能够做到上面的5个原则那么这个原则自然就遵守了.

这篇关于胡侃软件之设计原则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/681962

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

软件设计师备考——计算机系统

学习内容源自「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 目录 1.1.1 计算机系统硬件基本组成 1.1.2 中央处理单元 1.CPU 的功能 1)运算器 2)控制器 RISC && CISC 流水线控制 存储器  Cache 中断 输入输出IO控制方式 程序查询方式 中断驱动方式 直接存储器方式(DMA)  ​编辑 总线 ​编辑

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

免费也能高质量!2024年免费录屏软件深度对比评测

我公司因为客户覆盖面广的原因经常会开远程会议,有时候说的内容比较广需要引用多份的数据,我记录起来有一定难度,所以一般都用录屏工具来记录会议内容。这次我们来一起探索有什么免费录屏工具可以提高我们的工作效率吧。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  录屏软件录屏功能就是本职,这款录屏工具在录屏模式上提供了多种选项,可以选择屏幕录制、窗口

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

HomeBank:开源免费的个人财务管理软件

在个人财务管理领域,找到一个既免费又开源的解决方案并非易事。HomeBank&nbsp;正是这样一个项目,它不仅提供了强大的功能,还拥有一个活跃的社区,不断推动其发展和完善。 开源免费:HomeBank 是一个完全开源的项目,用户可以自由地使用、修改和分发。用户友好的界面:提供直观的图形用户界面,使得非技术用户也能轻松上手。数据导入支持:支持从 Quicken、Microsoft Money

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

PDF 软件如何帮助您编辑、转换和保护文件。

如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案,还是尝试组织和编辑主文档,PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时,请考虑这些因素。 1. 确定您的 PDF 文档软件需求。 不同的 PDF 文档软件程序可以具有不同的功能,因此在决定哪个是最适合您的 PDF 软件之前,请花点时间评估您的