一看就懂的设计模式——六大原则(02)

单一职责原则

单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描述是这样的:A class or module should have a single reponsibility。一个类或者模块只负责完成一个功能。

从定义可知,在开发中不要设计大而全的类,要设计粒度小、功能单一的类。如果一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。

单一职责原则的优点:

  • 类的复杂性降低,实现什么职责都有明确的定义;
  • 逻辑变得简单,类的可读性提高了,而且,因为逻辑简单,代码的可维护性也提高了;
  • 变更的风险降低,因为只会在单一的类中的修改。

举个例子:
我们在做一个项目需要管理用户信息,我们有一个用户信息的类:

public class User{
	//修改用户信息
	public void Update(Userinfo userinfo){}
	//禁用某个用户
	public void DisableByUID(int uid){}
}

如果这时需要增加一个给客户发送邮件通知的功能,可能会写成下面这样

//错误的例子
public class User{
	//修改用户信息
	public void Update(Userinfo userinfo){}
	//禁用某个用户
	public void DisableByUID(int uid){}
	//发送邮件通知
	public void SendEmail(){}
}

这种写法就给User类增加了过多的功能,也不利于后期维护和复用,比方说管理员也需要增加发送邮件通知,比方说需要再增加发送短信通知,所以符合单一职责的写法就是将通知功能独立到一个类中

//用户类
pubilc class User{
	//修改用户信息
	public void Update(Userinfo userinfo){}
	//禁用某个用户
	public void DisableByUID(int uid){}
}
//通知类
public class Notification{
	//发送消息
	//MessageType 1:短信,2:邮件
	public void SendMessage(int MessageType, Message message){}
}
//消息体
public class Message{
	//发送人
	public string SendFrom {get;set;}
	//接受人
	public string SendTo{get;set;}	
	//消息内容
	public string Content{get;set;}
	//发送时间
	public datetime SentTime{get;set;}
}

开闭原则

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭

一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码实现变化。之所以不建议修改已有代码,是因为一旦对原有的代码进行修改,就有可能影响到原有的模块,引起bug,修改完还需要对项目中所有涉及到该模块的功能进行测试,徒增工作量。让对象对扩展开放,这要求我们要对需求的变更有一定的前瞻性和预见性,在实现层面,让代码依赖于抽象而非细节。比方说我们做的项目是使用SqlServer数据库,但我们知道该项目后期也可能更换成mysql数据库,就可以向下面这样实现

interface IDatabase{
	void Insert();
	void Delete();
	void Update();
	void Select();	
}

public class SqlServerDB : IDatabase{
	public void Insert();
	public void Delete();
	public void Update();
	public void Select();
}

public class MySqlDB : IDatabase{
	public void Insert();
	public void Delete();
	public void Update();
	public void Select();
}

//业务层在调用时,依赖抽象,也可以通过配置参数来实现实例化不同的数据库操作类
IDatabase db = new SqlServerDB();
db.Delete();

里氏替换原则

如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都替换成o2的时候,程序P的行为都没有发生变化,那么类型T2是类型T1的子类型。

说人话就是:

只要父类能出现的地方子类就可以出现。

这个原则定义了4层含义:

  1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
  2. 子类可以有自己的个性,可以有自己的属性和方法。
  3. 子类覆盖或重载父类的方法时输入参数可以被放大。
  4. 子类覆盖或重载父类的方法时输出结果可以被缩小,也就是说返回值要小于或等于父类的方法返回值。

在软件开发过程中,子类替换父类后,程序的行为应该是一样的。只有当子类替换掉父类后,此时软件的功能不受影响,父类才能真正地被复用,子类才能在父类的基础上添加新的行为。下面是一个错误的示范:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}
  
public override class C1 : C{
    public int func(int a, int b) {
        return a-b;
    }
}
  
public class Client{
    public static void main(String[] args) {
        C c = new C1();
        Console.WriteLine("2+1=" + c.func(2, 1));
    }
}

运行结果:2+1=1
子类重写了父类C的func方法,导致引用父类的地方并不能透明的使用子类的对象,违背里氏替换原则。正确的做法是在子类中重新写一个心的方法:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}
  
public class C1 : C{
    public int func2(int a, int b) {
        return a-b;
    }
}
  
public class Client{
    public static void main(String[] args) {
        C1 c = new C1();
        Console.WriteLine("2-1=" + c.func2(2, 1));
    }
}

依赖倒置原则

高层模块不应该依赖底层模块,两者都应该依赖其抽象;
抽象不应该依赖细节;
细节应该依赖抽象;

在C#中点抽象指的是接口和抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,特点就是可以直接被实例化。所以该原则可以这么理解:

模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
接口或抽象类不依赖于实现类
实现类应该依赖接口或抽象类

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。依赖倒置原则的核心思想是面向接口编程,如果理解了面向接口编程的概念也就自然掌握依赖倒置原则,网上有个读书读报纸的例子很形象的解释了该原则:

class Book{
	public String getContent(){
		return "从前有座山,山里有个庙……";
	}
}
 
class Mother{
	public void narrate(Book book){
		Console.WriteLine("妈妈开始讲故事");
		Console.WriteLine(book.getContent());
	}
}
 
public class Client{
	public static void main(String[] args){
		Mother mother = new Mother();
		mother.narrate(new Book());
	}
}

如果现在要增加读报纸的功能

class Newspaper{
	public String getContent(){
		return "今日热点新闻";
	}
}

这时不得不修改Mother类才能读报纸,这样不科学,所以我们引入接口的概念来重构这段代码


interface IReader{
	public String getContent();
}

定义个读的接口,然后让读书和读报纸都继承该接口,此时Mother就能读各种内容了

class Newspaper implements IReader {
	public String getContent(){
		return "今日热点新闻";
	}
}
class Book implements IReader{
	public String getContent(){
		return "从前有座山,山里有个庙……";
	}
}
 
class Mother{
	public void narrate(IReader reader){
		Console.WriteLine("妈妈开始讲故事");
		Console.WriteLine(reader.getContent());
	}
}
 
public class Client{
	public static void main(String[] args){
		Mother mother = new Mother();
		mother.narrate(new Book());
		mother.narrate(new Newspaper());
	}
}

接口隔离原则

客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口之上

接口隔离原则将非常庞大、臃肿的接口拆分成更小更具体的接口。一个接口如果定义的过于臃肿,则代表它的每一个实现类都要考虑所有的实现逻辑,无形增加了维护的成本。

举例来说,所有云服务供应商都与阿里云一样提供相同种类的功能。但当你着手为其他供应商提供支持时, 程序库中绝大部分的 接口会显得过于宽泛。其他云服务供应商没有提供部分方法 所描述的功能。
在这里插入图片描述
那腾讯云中没有接口定义的3个方法,这种情况就是违反了接口隔离原则,正确的做法是将接口拆分成更小的粒度,如下:
在这里插入图片描述

迪米特法则

一个对象应该对其他对象有最小的了解。如果两个类不必彼此直接通信,那么这两个类就应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

迪米特法则强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开,其实就是在封装一个类的时候尽量明确这个类的属性和操作属性的方法。迪米特法则的根本思想,是强调类自己的松耦合。类之间的松耦合越弱,越有利于复用。

来看个错误的例子:

public class LODErrorTest {

    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.readBook();
    }

}


/**
 * 错误的示范
 */
public class Phone {
    App app = new App();
    //关键是下面这行代码
    Book book = new Book("设计模式");
    public void readBook() {
        app.read(book);
    }

}


public class App {
    
    public void read(Book book) {
        Console.WriteLine(book.getTitle());
    }

}

public class Book {

    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

在手机类中,手机和书籍是没有关系的,书籍应该是在阅读软件里面,所以应该将手机与书籍隔离开,正确的写法是这样子的:

public class LODRightTest {

    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.readBook();
    }

}

/**
 * 正确的示范
 */
public class Phone {

    private App app = new App();

    public void readBook() {
        app.read();
    }

}


public class App {
    private Book book = new Book("设计模式");

    public void read() {
        Console.WriteLine(book.getTitle());
    }

}

public class Book {

    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
使用 JavaScript 编写的 Squareshooter 游戏及其源代码   项目:使用 JavaScript 编写的 Squareshooter 游戏(附源代码) 这款游戏是双人游戏。这是一款使用 JavaScript 编写的射击游戏,带有门户和强化道具。在这里,每个玩家都必须控制方形盒子(作为射手)。这款射击游戏的主要目标是射击对手玩家以求生存。当它射击对手时,它会获得一分。 游戏制作 该游戏仅使用 HTML 和 JavaScript 开发。该游戏的 PC 控制也很简单。 对于玩家 1: T:朝你上次动作的方向射击 A:向左移动 D:向右移动 W:向上移动 S:向下移动 对于玩家2: L:朝你上次移动的方向射击 左箭头:向左移动 右箭头:向右移动 向上箭头:向上移动 向下箭头:向下移动 游戏会一直进行,直到您成功射击对手或对手射击您为止。游戏得分显示在顶部。所有游戏功能均由 JavaScript 设置,而布局和其他次要功能则由 HTML 设置。 如何运行该项目? 要运行此项目,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox。要运行此游戏,首先,通过单击 index.html 文件在浏览器中打开项目。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值