面向对象之七大基本原则(javaScript)
1. 前言
面向对象编程有自己的特性与原则,如果对于面向对象有一些了解的话,面向对象三大特征,封装、继承、多态,如果对面向对这三个概念不太了解,请参考面向对象之三个基本特征(javaScript)。
2. 单一职责
如果我们在编写程序的时候,一类或者一个方法里面包含了太多方法,对于代码的可读性来说,无非是一场灾难,对于我们来说。所以为了解决这个问题,出现了单一职责。
什么是单一职责
单一职责:又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。(节选自百度百科)
按照上面说的,就是对一个类而言,应该仅有一个引起它变化的原因。换句话说,一个类的功能要单一,只做与它相关的事情。在类的设计过程中要按职责进行设计,彼此保持正交,互不干涉。
单一职责的好处
- 类的复杂性降低,实现什么职责都有清晰明确的定义
- 可读性提高,复杂性降低,那当然可读性提高了
- 可维护性提高,可读性提高,那当然更容易维护了
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
实例
class ShoppinCar {
constructor(){
this.goods = [];
}
addGoods(good){
this.goods = [good];
}
getGoodsList(){
return this.goods;
}
}
class Settlement {
constructor(){
this.result = 0;
}
calculatePrice(list,key){
let allPrice = 0;
list.forEach((el) => {
allPrice += el[key];
})
this.result = allPrice;
}
getAllPrice(){
return this.result;
}
}
用上面的代码来说ShoppinCar
类存在两个方法addGoods
和getGoodsList
,分别是添加商品和获取商品列表。Settlement
类中存在两个方法calculatePrice
和getAllPrice
分别做的事情是计算价钱与获取总价钱。ShoppinCar
与Settlement
都是在做自己的事情。添加商品与计算价格,虽然在业务上是相互依赖的,但是在代码中分别用两个类,然他们自己做自己的事情。其中任何一个类更改不会对另一个类进行更改。
3. 开闭原则
在一个类中暴露出去的方法,若这个方法变更了,则会产生很大的后果,可能导致其他依赖于这个方法且有不需要变更的业务造成大面积瘫痪。为了解决这个问题,可以单独再写一个方法,若这个方法与这个类中的其他方法相互依赖。
解决办法:
- 把其中依赖的代码copy一份到新的类中。
- 在新类中引用旧类中的方法。
两种方法都不是最好的解决方案。
第一种方法会导致代码大量的重复,第二种方法会导致类与类之间互相依赖。
什么是开闭原则
开闭原则:“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。(节选自百度百科)
开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。开闭原则是一个最基本的原则,另外六个原则都是开闭原则的具体形态,是指导设计的工具和方法,而开闭原则才是精神领袖.
开闭原则好处
- 开闭原则有利于进行单元测试
- 开闭原则可以提高复用性
- 开闭原则可以提高可维护性
- 面向对象开发的要求
实例
class Drag {
down(){
// ...
}
move(){
// ...
// 对拖拽没有做任何限制可以随意拖拽
}
up(){
// ...
}
}
class LimitDrag extends Drag {
move(){
// ...
// 重写该方法对拖拽进行限制处理
}
}
在LimitDrag
中重写了move
方法,若修改了可以满足两种需求,一种是限制型拖拽,一种是不限制型拖拽,任何一个更改了另外一个还是可以正常运行。
4. 里氏替换
每个开发人员在使用别人的组件时,只需知道组件的对外裸露的接口,那就是它全部行为的集合,至于内部到底是怎么实现的,无法知道,也无须知道。所以,对于使用者而言,它只能通过接口实现自己的预期,如果组件接口提供的行为与使用者的预期不符,错误便产生了。里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。
什么是里氏替换
里氏替换原则:OCP作为OO的高层原则,主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构,维持设计的封闭性。“抽象”是语言提供的功能。“多态”由继承语义实现。(节选自百度百科)
里氏替换好处
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
- 提高代码的重用性
- 子类可以形似父类,但是又异于父类。
- 提高代码的可扩展性,实现父类的方法就可以了。许多开源框架的扩展接口都是通过继承父类来完成。
- 提高产品或项目的开放性
实例
// 抽象枪类
class AbstractGun {
shoot(){
throw "Abstract methods cannot be called";
}
}
// 步枪
class Rifle extends AbstractGun {
shoot(){
console.log("步枪射击...");
}
}
// 狙击枪
class AUG extends Rifle {
zoomOut(){
console.log("通过放大镜观察");
}
shoot(){
console.log("AUG射击...");
}
}
// 士兵
class Soldier {
constructor(){
this.gun = null;
}
setGun(gun){
this.gun = gun;
}
killEnemy(){
if(!this.gun){
throw "需要给我一把枪";
return;
}
console.log("士兵开始射击...");
this.gun.shoot();
}
}
// 狙击手
class Snipper extends Soldier {
killEnemy(aug){
if(!this.gun){
throw "需要给我一把枪";
return;
}
this.gun.zoomOut();
this.gun.shoot();
}
}
let soldier = new Soldier();
soldier.setGun(new Rifle());
soldier.killEnemy();
let snipper = new Snipper();
// 分配狙击枪
snipper.setGun(new AUG());
snipper.killEnemy();
snipper.setGun(new Rifle());
// snipper.killEnemy(); // this.gun.zoomOut is not a function
从上述代码中可以看出,子类和父类之间关系,子类方法一定是等于或大于父类的方法。子类能够出现的父类不一定能出现,但是父类出现的地方子类一定能够出现。
5. 依赖倒置
如果方法与方法之间或类与类之间,存在太多的依赖关系会导致代码可读性以及可维护性很差。依赖倒置原则能够很好的解决这些问题。
什么是依赖倒置
依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。(节选自百度百科)
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
依赖倒置好处
- 通过依赖于接口,隔离了具体实现类
- 低一层的变动并不会导致高一层的变动
- 提高了代码的容错性、扩展性和易于维护
实例
// 抽象枪类
class AbstractGun {
shoot(){
throw "Abstract methods cannot be called";
}
}
// 步枪
class Rifle extends AbstractGun {
shoot(){
console.log("步枪射击...");
}
}
// 狙击枪
class AUG extends AbstractGun {
shoot(){
console.log("AUG射击...");
}
}
从上面的代码可以看出,步枪与狙击枪的shoot
全部都是依赖于AbstractGun
抽象的枪类,上述编程满足了依赖倒置原则。
6. 接口隔离
什么是接口隔离
接口隔离:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。(节选自百度百科)
接口隔离原则与单一职责原则的审视角度不相同。单一职责原则要求是类和接口的职责单一,注重的职责,这是业务逻辑上的划分。接口隔离原则要求接口的方法尽量少。
接口隔离好处
- 避免接口污染
- 提高灵活性
- 提供定制服务
- 实现高内聚
实例
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
class Behavior {
eat(){
throw "Abstract methods cannot be used";
}
call(){
throw "Abstract methods cannot be used";
}
}
class Action {
climbTree(){
throw "Abstract methods cannot be used";
}
}
class Dog extends Behavior{
eat(food){
console.log(`狗正在吃${food}`);
}
hungry(){
console.log("汪汪汪,我饿了")
}
}
const CatMin = mix(Behavior,Action);
class Cat extends CatMin{
eat(food){
console.log(`猫正在吃${food}`);
}
hungry(){
console.log("喵喵喵,我饿了")
}
climbTree(){
console.log("爬树很开心哦~")
}
}
let dog = new Dog();
dog.eat("骨头");
dog.hungry();
let cat = new Cat();
cat.eat("鱼");
cat.hungry();
cat.climbTree();
大家一定要好好分析一下上面的代码,共有两个抽象类,分别对应不同的行为,Cat
与Dog
类拥有共同的行为,但是Cat
又拥有其自己单独的行为,使用抽象(即接口)继承其方法,使用接口隔离使其完成各自的工作,各司其职。
7. 迪米特法则
迪米特法则:最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.(节选自百度百科)
迪米特法则的做法观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。一个类应该对其他对象保持最少的了解。通俗来讲,就是一个类对自己依赖的类知道的越少越好。因为类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
迪米特法则好处
- 减少对象之间的耦合性
实例
class ISystem {
close(){
throw "Abstract methods cannot be used";
}
}
class System extends ISystem{
saveCurrentTask(){
console.log("saveCurrentTask")
}
closeService(){
console.log("closeService")
}
closeScreen(){
console.log("closeScreen")
}
closePower(){
console.log("closePower")
}
close(){
this.saveCurrentTask();
this.closeService();
this.closeScreen();
this.closePower();
}
}
class IContainer{
sendCloseCommand(){
throw "Abstract methods cannot be used";
}
}
class Container extends IContainer{
constructor(){
super()
this.system = new System();
}
sendCloseCommand(){
this.system.close();
}
}
class Person extends IContainer{
constructor(){
super();
this.container = new Container();
}
clickCloseButton(){
this.container.sendCloseCommand();
}
}
let person = new Person();
person.clickCloseButton();
上面代码中Container
作为媒介,其调用类不知道其内部是如何实现,用户去触发按钮,Container
把消息通知给计算机,计算机去执行相对应的命令。
8. 组合/聚合复用原则
聚合(Aggregation)表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象但B对象不是A对象的一部分。
合成(Composition)则是一种强的'拥有'关系,体现了严格的部分和整体关系,部分和整体的生命周期一样。
组合/聚合:是通过获得其他对象的引用,在运行时刻动态定义的,也就是在一个对象中保存其他对象的属性,这种方式要求对象有良好定义的接口,并且这个接口也不经常发生改变,而且对象只能通过接口来访问,这样我们并不破坏封装性,所以只要类型一致,运行时还可以通过一个对象替换另外一个对象。
优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上,这样类和类继承层次会保持较小规模,而且不太可能增长为不可控制的庞然大物。
组合/聚合复用原则好处
- 新的实现较为容易,因为超类的大部分功能可通过继承关系自动进入子类;
- 修改或扩展继承而来的实现较为容易。
实例
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
class Savings {
saveMoney(){
console.log("存钱");
}
withdrawMoney(){
console.log("取钱");
}
}
class Credit {
overdraft(){
console.log("透支")
}
}
const CarMin = mix(Savings,Credit);
class UserCar extends CarMin {
constructor(num,carUserName){
super();
console.log()
this.carNum = num;
this.carUserName = carUserName;
}
getCarNum(){
return this.carNum;
}
getCarUserName(){
return this.carUserName;
}
}
let myCar = new UserCar(123456798,"Aaron");
console.log(myCar.getCarNum());
console.log(myCar.getCarUserName());
myCar.saveMoney();
myCar.withdrawMoney();
myCar.overdraft();
9. 总结
这些原则在设计模式中体现的淋淋尽致,设计模式就是实现了这些原则,从而达到了代码复用、增强了系统的扩展性。所以设计模式被很多人奉为经典。我们可以通过好好的研究设计模式,来慢慢的体会这些设计原则。
面向对象之七大基本原则(javaScript)的更多相关文章
- 【blade04】用面向对象的方法写javascript坦克大战
前言 javascript与程序的语言比如C#或者java不一样,他并没有“类”的概念,虽然最新的ECMAScript提出了Class的概念,我们却没有怎么用 就单以C#与Java来说,要到真正理解面 ...
- 【设计模式系列】之OO面向对象设计七大原则
1 概述 本章叙述面向向对象设计的七大原则,七大原则分为:单一职责原则.开闭原则.里氏替换原则.依赖倒置原则.接口隔离原则.合成/聚合复用原则.迪米特法则. 2 七大OO面向对象设计 2.1 单一 ...
- 面向对象,更适合JavaScript
面向对象程序设计是软件开发中一个很庞大很复杂的话题,它并不是仅仅学会类.继承.封装.多态这些面向对象编程语法元素就表示掌握的,这些语法元素只是实现面向对象程序的工具, 就像砖块.水泥能搭建小屋,也能造 ...
- Python设计模式 - 基础 - 七大基本原则
提倡使用设计模式,主要出发点就是实现代码复用,增加代码的扩展性和可维护性.如何设计出简洁.易懂.灵活.优美的代码结构的确是一门学问,透彻理解并践行如下七大原则通常都能取得基本满意的结果: - 单一职责 ...
- JavaScript面向对象OOM 2(JavaScript 创建对象的工厂模式和构造函数模式)
在创建对象的时候,使用对象字面量和 new Object() 构造函数的方式创建一个对象是最简单最方便的方式.但是凡是处于初级阶段的事物都会不可避免的存在一个问题,没有普适性,意思就是说我要为世界 ...
- C#面向对象(五大基本原则 )
五大原则 单一职责原则(SRP)开放封闭原则(OCP) 里氏替换原则(LSP) 依赖倒置原则(DIP) 接口隔离原则(ISP) 一.单一职责原则SRP(Single Responsibility P ...
- 全方位深入理解JavaScript面向对象
JavaScript面向对象程序设计 转载:https://blog.csdn.net/lihangxiaoji/article/details/79753473#72__871 本文会碰到的知识点: ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- javascript面向对象系列第一篇——构造函数和原型对象
× 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...
随机推荐
- bzoj 2038 小z的袜子 莫队
莫队大法好,入坑保平安 只要能O(1)或O(log)转移,离线莫队貌似真的无敌. #include<cstdio> #include<iostream> #include< ...
- zookeeper(zkCli)命令概览
连接: ./zkCli.sh -timeout 0 -r -server ip:port -timeout:当前会话的超时时间,zookeper依靠与客户端的心跳来判断会话是否有效,单位是毫秒-r: ...
- Hive 查询元数据库获取某个分区的count数
=========查询分区的大小========= select d.NAME,t.TBL_NAME,p.PART_NAME,prm.PARAM_KEY,prm.PARAM_VALUE from TB ...
- ThreadLocal的应用与实现原理
本文对ThreadLocal的分析基于JDK 8. 本文大纲 1. ThreadLocal快速上手 2. ThreadLocal应用场景 3. TheadLocal set与get方法简析 4. Th ...
- Linux学习_012_Centos 6.8 安装 Netcat
测试中,需要通过 Netcat 发送数据. 配置环境:CentOS 6.8 1.下载安装包到指定目录,例如本博主的是:/opt/software/ wget https://sourceforge.n ...
- 聊聊 JUC 并发包
今天开始我们聊聊 Java 并发工具包中提供的一些工具类,本文主要从并发同步容器和并发集合工具角度入手,简单介绍下相关 API 的用法与部分实现原理,旨在帮助大家更好的使用和理解 JUC 工具类. 在 ...
- 利用Asp.Net Core的MiddleWare思想处理复杂业务流程
最近利用Asp.Net Core 的MiddleWare思想对公司的古老代码进行重构,在这里把我的设计思路分享出来,希望对大家处理复杂的流程业务能有所帮助. 背景 一个流程初始化接口,接口中根据传入的 ...
- 面试官,你再问我 Bit Operation 试试?
在面试环节中,面试官很喜欢问一些特别的题目,这些题目有着特殊的解法,如果回答的巧妙往往能在面试中加分. 在这些题目中,位操作(Bit Operation)就是极具魅力的一种.今天,吴师兄就来分享 Le ...
- Fundebug支付宝小程序BUG监控插件更新至0.2.0,新增test()方法,报错增加Page数据
摘要: 0.2.0新增fundebug.test()方法,同时报错增加了Page数据. Fundebug提供专业支付宝小程序BUG监控服务,可以第一时间为您捕获生存环境中小程序的异常.错误或者BUG, ...
- vue + element + 初始化项目
前提:已经安装了好了npm 和 vue脚手架 注意: 进入想要放置项目的目录下. 1.vue init webpack sun-vue-element 2.根据提示输入 y/n 3.npm run d ...