快速理解-SOLID-面向对象设计接口隔离原则

快速理解 SOLID (面向对象设计)——接口隔离原则在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转),指代了面向对象编程和面向对象设计的五个基本原则。当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。1. 接口隔离原则1.1 接口隔离原则 的定义多个特定客户端接口要好于一个宽泛用途的接口,简单来说就是接口版的单一职责原则。 1.2 接口隔离原则 解决了什么问题类A通过接口I依赖于类B,类C通过接口I依赖于类D。而往往类B和类D都实现类A和类B不需要的方法。 1.3 接口隔离原则 举个例子 在上面的例子中,UserDataController类通过接口依赖于UserDataService类,UserAuthController类通过接口依赖于UserAuthService类。但是他们都需要实现IUserService接口的所有方法,这样使代码出现了很多不必要的代码。 而有些人说,为什么不把UserDataService类和UserAuthService类合在一起呢?因为合在一起又违反了单一职责原则。 在上面的例子中,将IUserService接口拆分为不同的小接口,需要什么功能就实现什么接口。这样就避免了很多不必要的代码。 1.4 接口隔离原则 的总结定义接口尽可能的小,需要什么功能就实现什么接口。提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。2. 关注我的微信公众号,查看更多文章,第一时间收到我的文章。 SOLID (面向对象设计)——单一职责原则,你学会了吗?

September 8, 2019 · 1 min · jiezi

快速理解-SOLID-面向对象设计里氏替换原则

快速理解 SOLID (面向对象设计)——里氏替换原则在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转),指代了面向对象编程和面向对象设计的五个基本原则。当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。1. 里氏替换原则1.1 里氏替换原则 的定义里氏替换原则这个名字使很多人产生了疑惑,其实里氏替换原则是由麻省理工学院的一位姓里的女士提出来的。派生类(子类)对象可以在程式中代替其基类(父类)对象。 1.2 里氏替换原则 解决了什么问题职责P是由类A实现的,现在需要对职责P进行扩展,扩展为职责P+。而职责P+由类A的子类B实现,则在子类B完成职责P+后,有可能破坏了原有的职责P。 1.3 里氏替换原则 举个例子public class User { // 增加积分 public void addCount(){ this.count = this.count + 10; }}上面这个User类可以看作有一个职责:用户增加积分。 而现在要求VIP用户不但增加积分而且增加经验: //不符合 里氏替换原则public class VIPUser extends User{ // 增加积分而且增加经验 public void addCount(){ this.count = this.count + 10; this.experience = this.experience + 10; }}上面这个VIPUser类通过重写来实现增加积分职责的扩展,但是也破坏了原有的增加积分职责。如果将使用User类的地方替换为VIPUser类,会因为addCount函数发生改变而产生错误。所以不符合里氏替换原则。 //符合 里氏替换原则public class VIPUser extends User{ // 增加积分而且增加经验 public void addCountVIP(){ super.addCount(); this.experience = this.experience + 10; }}上面这个VIPUser类通过调用父类方法来实现增加积分职责的扩展,没有破环原有的增加积分职责。而且任何使用User类的地方都可以替换为VIPUser类。所以符合里氏替换原则。 ...

September 8, 2019 · 1 min · jiezi

快速理解-SOLID-面向对象设计单一职责原则

快速理解 SOLID (面向对象设计)——单一职责原则在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转),指代了面向对象编程和面向对象设计的五个基本原则。当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。1. 单一职责原则1.1 单一职责原则 的定义不要存在多于一个导致类变更的原因。简单的讲,认为对象应该仅具有一种单一功能。 1.2 单一职责原则 解决了什么问题类A 负责两个职责P1、职责P2。当由于职责P1需求发生改变时,而需要在类A中修改职责P1。有可能使原本正常运行的职责P2发生故障。 1.3 单一职责原则 举个例子//不符合 单一职责原则public class UserService { // ...增加 User 功能 // ...删除 User 功能 // ...更新 User 功能 // ...查询 User 功能 // ...User 登录功能}上面这个UserService类可以看作有两个职责: 对 User 数据的操作。对 User 验证。根据单一职责原则应该将这两个职责分别放在两个类中: //符合 单一职责原则public class UserDataService { // ...增加 User 功能 // ...删除 User 功能 // ...更新 User 功能 // ...查询 User 功能}public class UserAuthService { // ...User 登录功能}1.4 单一职责原则 的优点降低类的复杂度,一个类只负责一种职责。提高代码的可读性。降低更改需求带来的风险,降低对其他功能的影响。2. 关注我的微信公众号,查看更多文章,第一时间收到我的文章。 ...

September 8, 2019 · 1 min · jiezi

发布订阅模式和观察者模式真的不一样

背景设计模式的定义: 在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。设计模式并不能直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,它不是一个死的机制,它是一种思想,一种代码的形式。 每种语言对于各种设计模式都要它们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂模式就不适用于JavaSctipt。模式应该用在正确的地方,而所谓正确的地方只有我们深刻理解模式的意图后,再结合项目的实际场景才知道。 观察者模式 (Observer Pattern)观察者模式定义了对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新。观察者模式属于行为模式,行为模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。 观察者模式还有一个别名叫“发布-订阅模式”,又或者“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐各通知订阅者。我们用报纸期刊的订阅来举例说明,当你订阅一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是定义里描述的“一对多”的依赖关系。 发布-订阅模式 (Pub-Sub Pattern)其实24种基本设计模式中,并没有发布-订阅模式,上面也解释了,它只是观察者模式的一个别称。但经过时间的沉淀,它已经强大起来,已经独立于观察者模式,成为一种新的设计模式。 在现在的发布-订阅模式中,发布者的消息不会直接发送给订阅者,这意味着发布者和订阅者都不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息,并相应的分发给它们的订阅者。举个例子,你在微博上关注了A,同时其他很多人也关注了A,当A发布动态时,微博就会为你们推送这条动态。A就是发布者,你就是订阅者,微博是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的。 观察者模式和发布-订阅模式有什么区别?我们先来看下者两个模式的实现结构: 观察者模式: 观察者(Observer)直接订阅(Subscribe)主体(Subject),而当主体被激活时,会触发(Fire Event)观察者里的事件。 发布-订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 案例观察者模式// 有一家猎人公会,其中每个猎人都具体发布任务(publish),订阅任务(subscribe)的功能// 它们都有一个订阅列表记录谁订阅了自己// 定义一个猎人,包括姓名、级别、订阅列表function Hunter(name, level) { this,name = name this.level = level this.list = []}Hunter.prototype.publish = function (money) { console.log(this,level + '猎人: ' + this.name + '寻求帮助') this.list.forEach(function (callback) { callback && callback(money) })}Hunter.prototype.subscribe = function (target, callback) { console.log(this.level + '猎人: ' + this.name + '订阅了: ' + target.name) target.list.push(callback)}// 猎人公会走注册了几个猎人var hunterZhang = new Hunter('张三', '钻石')var hunterLi = new Hunter('李四', '黄金')var hunterWang = new Hunter('王五', '白银')var hunterZhao = new Hunter('赵六', '青铜')// 赵六等级较低,可能需要帮助,所以张三、李四、王五都订阅了赵六hunterZhang.subscribe(hunterZhao, function (money) { console.log('小明表示: ' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')})hunterLi.subscribe(hunterZhao, function () { console.log('李四表示: 给予帮助')})hunterWang.subscribe(hunterZhao, function () { console.log('王五表示: 给予帮助')})// 赵六遇到困难,悬赏198寻求帮助hunterZhao.publish(198)// 猎人们(观察者)关联他们感兴趣的猎人(目标对象),如赵六,当赵六有困难时,会自动通知给他们(观察者)发布-订阅模式// 定义了一家猎人公会// 主要功能包含任务发布大厅(topics)、订阅任务(subscribe)、发布任务(publish)var HunterUnion = { type: 'hunt', topics: Object.create(null), subscribe: function (topic, callback) { if (!this.topics[topic]) { this.topics[topic] = [] } this.topics[topic].push(callback) }, publish: function (topic, money) { if (!this.topics[topic]) { return } for(var cb of this.topics[topic]) { cb(money) } }}// 定义一个猎人类,包括姓名和级别function Hunter(name, level) { this.name = name this.level = level}// 猎人可以在猎人公会发布、订阅任务Hunter.prototype.subscribe = function (task, fn) { console.log(this.level + '猎人: ' + this.name + '订阅了狩猎: ' + task + '的任务') HunterUnion.subscribe(task, fn)}Hunter.prototype.publish = function (task, money) { console.log(this.level + '猎人: ' + this.name + '发布了狩猎: ' + task + '的任务') HunterUnion.publish(task, money)}//猎人工会注册了几个猎人let hunterZhang = new Hunter('张三', '钻石')let hunterLi = new Hunter('李四', '黄金')let hunterWang = new Hunter('王五', '白银')let hunterZhao = new Hunter('赵六', '青铜')//张三,李四,王五分别订阅了狩猎tiger的任务hunterZhang.subscribe('tiger', function(money){ console.log('张三表示:' + (money > 200 ? '' : '不') + '接取任务')})hunterLi.subscribe('tiger', function(money){ console.log('李四表示:接取任务')})hunterWang.subscribe('tiger', function(money){ console.log('王五表示:接取任务')})//赵六订阅了狩猎sheep的任务hunterZhao.subscribe('sheep', function(money){ console.log('赵六表示:接取任务')})//赵六发布了狩猎tiger的任务hunterZhao.publish('tiger', 198)//猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布-订阅模式最大的区别: 发布-订阅模式有事件调度中心。 ...

August 28, 2019 · 2 min · jiezi

为什么用枚举类来实现单例模式越来越流行

前言单例模式是 Java 设计模式中最简单的一种,只需要一个类就能实现单例模式,但是,你可不能小看单例模式,虽然从设计上来说它比较简单,但是在实现当中你会遇到非常多的坑,所以,系好安全带,上车。 单例模式的定义单例模式就是在程序运行中只实例化一次,创建一个全局唯一对象,有点像 Java 的静态变量,但是单例模式要由于静态变量,静态变量在程序启动的时候就要创建,会造成大量的资源浪费,好的单例模式不会有这个问题。开发中的很多工具类都应用了单例模式,线程池、缓存、日志对象等,它们都只需要创建一个对象,如果创建多份实例,可能会带来不可预知的问题,比如资源的浪费、结果处理不一致等问题。 单例的实现思路静态化实例对象私有化构造方法,禁止通过构造方法创建实例提供一个公共的静态方法,用来返回唯一实例单例的好处只有一个对象,内存开支少、性能好(当一个对象的产生需要比较多的资源,如读取配置、产生其他依赖对象时,可以通过应用启动时直接产生一个单例对象,让其永驻内存的方式解决)避免对资源的多重占用(一个写文件操作,只有一个实例存在内存中,避免对同一个资源文件同时写操作) 在系统设置全局访问点,优化和共享资源访问(如:设计一个单例类,负责所有数据表的映射处理)单例模式的实现单例模式的主流写法有饿汉模式、懒汉模式、双重检查锁模式、静态内部类单例模式、枚举类实现单例模式五种方式,其中懒汉模式、双重检查锁模式两种模式写法不当,会导致在多线程下不是单例或者单例出异常,后面将会给大家详细介绍。我们从最基本的饿汉模式开始我们的单例编写之路。 饿汉模式饿汉模式采用的是一种简单粗暴的形式,在定义静态属性时,直接实例化了对象。代码如下: //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快public class SingletonObject1 { // 利用静态变量来存储唯一实例 private static final SingletonObject1 instance = new SingletonObject1(); // 私有化构造函数 private SingletonObject1(){ // 里面可能有很多操作 } // 提供公开获取实例接口 public static SingletonObject1 getInstance(){ return instance; }}饿汉模式的优缺点优点JVM层面的线程安全,static关键字保证了在引用这个变量时,关于这个变量的所以写入操作都完成缺点不能实现懒加载,造成空间浪费,如果一个类比较大,我们在初始化的时就加载了这个类,但是我们长时间没有使用这个类,这就导致了内存空间的浪费。懒汉模式懒汉模式就像一个懒汉的时候,只有饿了才会想办法找东西来填饱肚子,从来不会先的准备好食物,以防饿了。懒汉模式实现了懒加载,解决了饿汉模式带来的空间浪费问题,实现了使用时才去初始化类,但是也引入了其他的问题,我们先来看看下面这个懒汉模式 public class SingletonObject2 { // 定义静态变量时,未初始化实例 private static SingletonObject2 instance; // 私有化构造函数 private SingletonObject2(){ } public static SingletonObject2 getInstance(){ // 使用时,先判断实例是否为空,如果实例为空,则实例化对象 if (instance == null) instance = new SingletonObject2(); return instance; }}上面这段懒汉模式实现代码,在多线程的情况下,不能保证是单例模式,主要问题出现在实例化对象的时候,所以我单独把实例化的代码提出来,给大伙讲讲为什么在多线程的情况下有可能会初始化多份实例。 ...

August 28, 2019 · 2 min · jiezi

JavaScript-设计模式系列-享元模式与资源池

享元模式 (Flyweight Pattern)运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。 享元模式的主要思想是共享细粒度对象,也就是说如果系统中存在多个相同的对象,那么只需共享一份就可以了,不必每个都去实例化每一个对象,这样来精简内存资源,提升性能和效率。 Fly 意为苍蝇,Flyweight 指轻蝇量级,指代对象粒度很小。 注意: 本文用到 ES6 的语法 let/const 、Class、Promise 等,如果还没接触过可以点击链接稍加学习 ~1. 你曾见过的享元模式我们去驾考的时候,如果给每个考试的人都准备一辆车,那考场就挤爆了,考点都堆不下考试车,因此驾考现场一般会有几辆车给要考试的人依次使用。如果考生人数少,就分别少准备几个自动档和手动档的驾考车,考生多的话就多准备几辆。如果考手动档的考生比较多,就多准备几辆手动档的驾考车。 我们去考四六级的时候(为什么这么多考试?????),如果给每个考生都准备一个考场,怕是没那么多考场也没有这么多监考老师,因此现实中的大多数情况都是几十个考生共用一个考场。四级考试和六级考试一般同时进行,如果考生考的是四级,那么就安排四级考场,听四级的听力和试卷,六级同理。 生活中类似的场景还有很多,比如咖啡厅的咖啡口味,餐厅的菜品种类,拳击比赛的重量级等等。 在类似场景中,这些例子有以下特点: 目标对象具有一些共同的状态,比如驾考考生考的是自动档还是手动档,四六级考生考的是四级还是六级;这些共同的状态所对应的对象,可以被共享出来;2. 实例的代码实现首先假设考生的 ID 为奇数则考的是手动档,为偶数则考的是自动档。如果给所有考生都 new 一个驾考车,那么这个系统中就会创建了和考生数量一致的驾考车对象: var candidateNum = 10 // 考生数量var examCarNum = 0 // 驾考车的数量/* 驾考车构造函数 */function ExamCar(carType) { examCarNum++ this.carId = examCarNum this.carType = carType ? '手动档' : '自动档'}ExamCar.prototype.examine = function(candidateId) { console.log('考生- ' + candidateId + ' 在' + this.carType + '驾考车- ' + this.carId + ' 上考试')}for (var candidateId = 1; candidateId <= candidateNum; candidateId++) { var examCar = new ExamCar(candidateId % 2) examCar.examine(candidateId)}console.log('驾考车总数 - ' + examCarNum)// 输出: 驾考车总数 - 10如果考生很多,那么系统中就会存在更多个驾考车对象实例,假如驾考车对象比较复杂,那么这些新建的驾考车实例就会占用大量内存。这时我们将同种类型的驾考车实例进行合并,手动档和自动档档驾考车分别引用同一个实例,就可以节约大量内存: ...

August 21, 2019 · 3 min · jiezi

JavaScript-设计模式系列-策略模式与动态表单验证

策略模式 (Strategy Pattern)又称政策模式,其定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。封装的策略算法一般是独立的,策略模式根据输入来调整采用哪个算法。关键是策略的实现和使用分离。 注意: 本文可能用到一些编码技巧比如 IIFE(Immediately Invoked Function Expression, 立即调用函数表达式),ES6 的语法 let/const、箭头函数、rest 参数,短路运算符 等,如果还没接触过可以点击链接稍加学习 ~1. 你曾见过的策略模式现在电子产品种类繁多,尺寸多种多样,有时候你会忍不住想拆开看看里面啥样(想想小时候拆的玩具车还有遥控器),但是螺丝规格很多,螺丝刀尺寸也不少,如果每碰到一种规格就买一个螺丝刀,家里就得堆满螺丝刀了。所以现在人们都用多功能的螺丝刀套装,螺丝刀把只需要一个,碰到不同规格的螺丝只要换螺丝刀头就行了,很方便,体积也变小很多。 再举个栗子,一辆车的轮胎有很多规格,在泥泞路段开的多的时候可以用泥地胎,在雪地开得多可以用雪地胎,高速公路上开的多的时候使用高性能轮胎,针对不同使用场景更换不同的轮胎即可,不需更换整个车。 这些都是策略模式的实例,螺丝刀/车属于封装上下文,封装和使用不同的螺丝刀头/轮胎,螺丝刀头/轮胎这里就相当于策略,可以根据需求不同来更换不同的使用策略。 在这些场景中,有以下特点: 螺丝刀头/轮胎(策略)之间相互独立,但又可以相互替换;螺丝刀/车(封装上下文)可以根据需要的不同选用不同的策略;2. 实例的代码实现具体的例子我们用编程上的例子来演示,比较好量化。 场景是这样的,某个电商网站希望举办一个活动,通过打折促销来销售库存物品,有的商品满 100 减 30,有的商品满 200 减 80,有的商品直接8折出售(想起被双十一支配的恐惧),这样的逻辑交给我们,我们要怎样去实现呢。 function priceCalculate(discountType, price) { if (discountType === 'minus100_30') { // 满100减30 return price - Math.floor(price / 100) * 30 } else if (discountType === 'minus200_80') { // 满200减80 return price - Math.floor(price / 200) * 80 } else if (discountType === 'percent80') { // 8折 return price * 0.8 }}priceCalculate('minus100_30', 270) // 输出: 210priceCalculate('percent80', 250) // 输出: 200通过判断输入的折扣类型来计算计算商品总价的方式,几个 if-else 就满足了需求,但是这样的做法的缺点也很明显: ...

August 21, 2019 · 4 min · jiezi

JavaScript-的一些常用设计模式

设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案设计模式是前人解决某个特定场景下对而总结出来的一些解决方案。可能刚开始接触编程还没有什么经验的时候,会感觉设计模式没那么好理解,这个也很正常。有些简单的设计模式我们有时候用到,不过没意识到也是存在的。 学习设计模式,可以让我们在处理问题的时候提供更多更快的解决思路。 当然设计模式的应用也不是一时半会就会上手,很多情况下我们编写的业务逻辑都没用到设计模式或者本来就不需要特定的设计模式。 适配器模式这个使我们常使用的设计模式,也算最简单的设计模式之一,好处在于可以保持原有接口的数据结构不变动。 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。例子适配器模式很好理解,假设我们和后端定义了一个接口数据结构为(可以理解为旧接口): [ { "label": "选择一", "value": 0 }, { "label": "选择二", "value": 1 }]但是后端后面因为其他原因,需要定义返回的结构为(可以理解为新接口): [ { "label": "选择一", "text": 0 }, { "label": "选择二", "text": 1 }]然后我们前端的使用到后端接口有好几处,那么我可以把新的接口字段结构适配为老接口的,就不需要各处去修改字段,只要把源头的数据适配好就可以了。 当然上面的是非常简单的场景,也是经常用到的场景。或许你会认为后端处理不更好了,的确是这样更好,但是这个不是我们讨论的范围。 单例模式单例模式,从字面意思也很好理解,就是实例化多次都只会有一个实例。 有些场景实例化一次,可以达到缓存效果,可以减少内存占用。还有些场景就是必须只能实例化一次,否则实例化多次会覆盖之前的实例,导致出现 bug(这种场景比较少见)。 例子实现弹框的一种做法是先创建好弹框, 然后使之隐藏, 这样子的话会浪费部分不必要的 DOM 开销, 我们可以在需要弹框的时候再进行创建, 同时结合单例模式实现只有一个实例, 从而节省部分 DOM 开销。下列为登入框部分代码: const createLoginLayer = function() { const div = document.createElement('div') div.innerHTML = '登入浮框' div.style.display = 'none' document.body.appendChild(div) return div}使单例模式和创建弹框代码解耦 const getSingle = function(fn) { const result return function() { return result || result = fn.apply(this, arguments) }}const createSingleLoginLayer = getSingle(createLoginLayer)document.getElementById('loginBtn').onclick = function() { createSingleLoginLayer()}代理模式代理模式的定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。代理对象拥有本体对象的一切功能的同时,可以拥有而外的功能。而且代理对象和本体对象具有一致的接口,对使用者友好。 ...

August 19, 2019 · 5 min · jiezi

javascript设计模式学习笔记之组合模式

组合模式组合模式是将对象组合成树形结构, 以表示 "部分-整体" 的层次结构, 再者, 利用对象的多态性统一对待组合对象和单个对象注意点: 组合模式不是父子关系对叶对象操作的一致性双向映射关系利用职责链模式提高组合模式性能应用场景 表示对象的部分-整体层次结构客户希望统一对待树中的所有对象举例: 扫描文件夹 /* * * * * * 文件夹(Folder) * * * * * * */var Folder = function(name) { this.name = name; this.files = []; this.parent = null;}Folder.prototype.add = function(file) { file.parent = this; this.files.push(file);}Folder.prototype.scan = function() { console.log('开始扫描文件夹:' + this.name); var i = 0, len = this.files.length, file; for(; i < len; i++) { file = this.files[i]; file.scan(); }}Folder.prototype.remove = function() { if (!this.parent) { return; } var i = this.parent.files.length - 1, files = this.parent.files, file; for(; i >= 0; i--) { file = files[i]; if (file === this) { files.splice(i, 1); } }}/* * * * * * 文件(File) * * * * * * */var File = function(name) { this.name = name; this.parent = null;}File.prototype.add = function() { throw new Error('文件下面不能再添加文件');}File.prototype.scan = function() { console.log('开始扫描文件:' + this.name);}File.prototype.remove = function() { if (!this.parent) { return; } var i = this.parent.files.length - 1, files = this.parent.files, file; for(; i >= 0; i--) { file = files[i]; if (file === this) { files.splice(i, 1); } }}

August 18, 2019 · 1 min · jiezi

Java描述设计模式07适配器模式

一、适配器模式简介1、基础概念适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。适配器模式有类适配器模式和对象适配器模式,以及缺省(接口)适配器,三种不同的形式。2、生活场景基于适配器模式,把220V的电压,转换为需要的110V电压。 public class C01_InScene { public static void main(String[] args) { CurrentAdapter adapter = new CurrentAdapter() ; System.out.println(adapter.get110VCurrent()) ; }}// 220V电流class Current220V { public int get220VCurrent (){ return 220 ; }}// 110V电流接口interface Current110V { int get110VCurrent () ;}// 电流适配器class CurrentAdapter extends Current220V implements Current110V { // 电流转换方法 @Override public int get110VCurrent() { int high = get220VCurrent() ; int low = high/2 ; return low ; }}二、类适配器1、模式简介类的适配器模式把适配的类的API转换成为目标类的API。 ...

August 18, 2019 · 2 min · jiezi

设计模式之桥接模式

0x01.定义与类型定义:将抽象部分与它的具体实现部分分离,使它们都可以独立地变化。桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。类型:结构型UML类图 Java实现/** * 主类抽象类 */public abstract class Abstraction { /** * 桥接组合对象 */ protected Implementor implementor; public Abstraction(Implementor implementor) { this.implementor = implementor; } /** * 操作类 */ public abstract void operation();}/** * 抽象接口 */public interface Implementor { void operationImpl();}/** * 实现类 */public class RefinedAbstraction extends Abstraction { public RefinedAbstraction (Implementor implementor) { super(implementor); } @Override public void operation() { System.out.println("操作"); implementor.operationImpl(); }}/** * 接口抽象实现1 */public class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { System.out.println("桥接A"); }}/** * 接口抽象实现2 */public class ConcreteImplementorB implements Implementor { @Override public void operationImpl() { System.out.println("桥接B"); }}测试与应用/** * 测试与应用 */public class Test { public static void main(String[] args) { Abstraction abstraction1 = new RefinedAbstraction(new ConcreteImplementorA()); Abstraction abstraction2 = new RefinedAbstraction(new ConcreteImplementorB()); abstraction1.operation(); abstraction2.operation(); }}输出结果操作桥接A操作桥接B角色介绍 ...

August 18, 2019 · 2 min · jiezi

Java设计模式优化单例模式

单例模式概述单例模式是一种对象创建模式,用于产生一个类的具体事例。使用单例模式可以确保整个系统中单例类只产生一个实例。有下面两大好处: 对于频繁创建的对象,节省初第一次实例化之后的创建时间。由于new操作的减少,会降低系统内存的使用频率。减轻GC压力,从而缩短GC停顿时间创建方式: 单例作为类的私有private属性单例类拥有私有private构造函数提供获取实例的public方法单例模式的角色: 角色作用单例类提供单例的工厂,返回类的单例实例使用者获取并使用单例类类基本结构: 单例模式的实现1.饿汉式public class HungerSingleton { //1.饿汉式 //私有构造器 private HungerSingleton() { System.out.println("create HungerSingleton"); } //私有单例属性 private static HungerSingleton instance = new HungerSingleton(); //获取单例的方法 public static HungerSingleton getInstance() { return instance; }}注意: 单例修饰符为static JVM加载单例类加载时,直接初始化单例。无法延时加载。如果此单例一直未被使用,单Singleton 因为调用静态方法被初始化则会造成内存的浪费。getInstance()使用static修饰,不用实例化可以直接使用Singleton.getInstance()获取单例。由于单例由JVM加载类的时候创建,所以不存在线程安全问题。2.简单懒汉式public class Singleton { //2.1简单懒汉式(线程不安全) //私有构造器 private Singleton() { System.out.println("create Singleton"); } //私有单例属性[初始化为null] private static Singleton instance = null; //获取单例的方法 public static Singleton getInstance() { if(instance == null) { //此处instance实例化 //首次调用单例时会进入 达成延时加载 instance = new Singleton(); } return instance; }}由于未使用 synchronized 关键字,所以当线程1调用单例工厂方法Singleton.getInstance() 且 instance 未初始化完成时,线程2调用此方法会将instance判断为null,也会将instance重新实例化赋值,此时则产生了多个实例!如需线程安全可以直接给getInstance方法上加synchronized关键字,如下:public class Singleton { //2.2简单懒汉式(线程安全) //私有构造器 private Singleton() { System.out.println("create Singleton"); } //私有单例属性[初始化为null] private static Singleton instance = null; //获取单例的方法 将此方法使用synchronized关键字同步 public static synchronized Singleton getInstance() { if(instance == null) { //此处instance实例化 //首次调用单例时会进入 达成延时加载 instance = new Singleton(); } return instance; }}面临的问题: ...

August 7, 2019 · 2 min · jiezi

JavaScript设计模式八组合模式

组合模式:又叫 “部分整体” 模式,将对象组合成树形结构,以表示 “部分-整体” 的层次结构。通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。生活小栗子:文件目录,DOM 文档树 模式特点表示 “部分-整体” 的层次结构,生成 "树叶型" 结构;一致操作性,树叶对象对外接口保存一致(操作与数据结构一致);自上而下的的请求流向,从树对象传递给叶对象;调用顶层对象,会自行遍历其下的叶对象执行。 代码实现树对象和叶对象接口统一,树对象增加一个缓存数组,存储叶对象。执行树对象方法时,将请求传递给其下叶对象执行。 // 树对象 - 文件目录class CFolder { constructor(name) { this.name = name; this.files = []; } add(file) { this.files.push(file); } scan() { for (let file of this.files) { file.scan(); } }}// 叶对象 - 文件class CFile { constructor(name) { this.name = name; } add(file) { throw new Error('文件下面不能再添加文件'); } scan() { console.log(`开始扫描文件:${this.name}`); }}let mediaFolder = new CFolder('娱乐');let movieFolder = new CFolder('电影');let musicFolder = new CFolder('音乐');let file1 = new CFile('钢铁侠.mp4');let file2 = new CFile('再谈记忆.mp3');movieFolder.add(file1);musicFolder.add(file2);mediaFolder.add(movieFolder);mediaFolder.add(musicFolder);mediaFolder.scan();/* 输出:开始扫描文件:钢铁侠.mp4开始扫描文件:再谈记忆.mp3*/CFolder 与 CFile 接口保持一致。执行 scan() 时,若发现是树对象,则继续遍历其下的叶对象,执行 scan()。 ...

July 16, 2019 · 1 min · jiezi

设计模式责任链模式

责任链模式概念描述责任链,顾名思义,就是用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。 适用场景现实中,请假的OA申请,请假天数如果是半天到1天,可能直接主管批准即可;如果是1到3天的假期,需要部门经理批准;如果是3天到30天,则需要总经理审批;大于30天,正常不会批准。 流程展示 类图设计为了实现上述场景,我们可以采用责任链设计模式。 员工提交请求类:LeaveRequest。抽象的请假责任处理类:AbstractLeaveHandler。直接主管审批处理类:DirectLeaderLeaveHandler。部门经理处理类:DeptManagerLeaveHandler。总经理处理类: GManagerLeaveHandler。员工请求发起申请到抽象的责任处理类中,根据员工的请假天数,对应的处理类完成处理。每一个责任处理类设置下面的节点。自身处理不了则传递给下一个节点处理。 编程实现LeaveRequest.java:@Setter@Getter@NoArgsConstructor@AllArgsConstructor@Builderpublic class LeaveRequest { /**天数*/ private int leaveDays; /**姓名*/ private String name;}AbstractLeaveHandler.java;/** * @program: cookbook * @description: 请假责任链抽象处理类 * @author: Byron * @create: 2019/07/11 09:34 */public abstract class AbstractLeaveHandler { /**直接主管审批处理的请假天数*/ protected int MIN = 1; /**部门经理处理的请假天数*/ protected int MIDDLE = 3; /**总经理处理的请假天数*/ protected int MAX = 30; /**领导名称*/ protected String handlerName; /**下一个处理节点(即更高级别的领导)*/ protected AbstractLeaveHandler nextHandler; /**设置下一节点*/ protected void setNextHandler(AbstractLeaveHandler handler){ this.nextHandler = handler; } /**处理请假的请求,子类实现*/ protected void handlerRequest(LeaveRequest request){ }}DirectLeaderLeaveHandler.java:/** * @program: cookbook * @description: 直接主管处理类 * @author: Byron * @create: 2019/07/11 09:46 */public class DirectLeaderLeaveHandler extends AbstractLeaveHandler{ public DirectLeaderLeaveHandler(String name) { this.handlerName = name; } @Override protected void handlerRequest(LeaveRequest request) { if(request.getLeaveDays() <= this.MIN){ System.out.println("直接主管:" + handlerName + ",已经处理;流程结束。"); return; } if(null != this.nextHandler){ this.nextHandler.handlerRequest(request); }else{ System.out.println("审批拒绝!"); } }}DeptManagerLeaveHandler.java:/** * @program: cookbook * @description: 部门经理处理类 * @author: Byron * @create: 2019/07/11 09:48 */public class DeptManagerLeaveHandler extends AbstractLeaveHandler { public DeptManagerLeaveHandler(String name) { this.handlerName = name; } @Override protected void handlerRequest(LeaveRequest request) { if(request.getLeaveDays() <= this.MIDDLE){ System.out.println("部门经理:" + handlerName + ",已经处理;流程结束。"); return; } if(null != this.nextHandler){ this.nextHandler.handlerRequest(request); }else{ System.out.println("审批拒绝!"); } }}GManagerLeaveHandler.java:/** * @program: cookbook * @description: 总经理处理类 * @author: Byron * @create: 2019/07/11 09:49 */public class GManagerLeaveHandler extends AbstractLeaveHandler { public GManagerLeaveHandler(String name) { this.handlerName = name; } @Override protected void handlerRequest(LeaveRequest request) { if(request.getLeaveDays() <= this.MAX){ System.out.println("总经理:" + handlerName + ",已经处理;流程结束。"); return; } if(null != this.nextHandler){ this.nextHandler.handlerRequest(request); }else{ System.out.println("审批拒绝!"); } }}演示ResponsibilityTest.java:public class ResponsibilityTest { public static void main(String[] args) { LeaveRequest request = LeaveRequest.builder().leaveDays(20).name("小明").build(); AbstractLeaveHandler directLeaderLeaveHandler = new DirectLeaderLeaveHandler("县令"); DeptManagerLeaveHandler deptManagerLeaveHandler = new DeptManagerLeaveHandler("知府"); GManagerLeaveHandler gManagerLeaveHandler = new GManagerLeaveHandler("京兆尹"); directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler); deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler); directLeaderLeaveHandler.handlerRequest(request); }}20天,运行输出: 总经理:京兆尹,已经处理;流程结束。1天,运行输出: 直接主管:县令,已经处理;流程结束。3天,运行输出: 部门经理:知府,已经处理;流程结束。35天,运行输出: 审批拒绝!总结责任链主要重在责任分离处理,让各个节点各司其职。责任链上的各个节点都有机会处理事务,但是也可能不会受理请求。责任链比较长,调试时可能会比较麻烦。责任链一般用于处理流程节点之类的实际业务场景中。Spring拦截器链、servlet过滤器链等都采用了责任链设计模式。链接CookBook

July 15, 2019 · 2 min · jiezi

js常用设计模式实现三建造者模式

创建型模式创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离关于创建型模式,已经接近尾声了,还剩下建造者模式和原型模式,这一篇说一说建造者模式 建造者模式的定义将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。很官方哈,举个例子,就拿他的名字来说,建房子,你尽管告诉开发商,我有钱我要一个这样的房子,然后开发商就会去找到工人,然后进行一些列的工作,最后工人干完之后,你住进去了,不关心过程,只要结果就是建造者模式 总之建造者模式就是通过一步步构建多个组成部分,直到构建一个对象 建造者模式实现根据上边的例子来实现一下建造者 首先你的需求就是你需要一个你想要的房子,那么你肯定不能自己去建造,首先你会找到开发商 /** * @author: 周靖松 * @information: 开发商 * @Date: 2019-07-14 10:11:54 */ class Developer { constructor() { this.need = ['卧室', '厨房', '客厅']; console.log("我需要这样的房间"); } construct() { console.log("开始建造"); let workerOk=this.need.map(el=>{ let builder = new CreatDiagram(); builder.build(el); return builder.getResult(); }) console.log("房子不错"); console.log(workerOk); } } // 要求产品 let home = new Developer(); // 生成产品 home.construct();这是最终的目的,在我们new这个开发商类的时候,我们告诉他我们需要什么样的房子,需要有什么,然后开发商也不是自己去建造,他需要一个设计图,并且需要一些工人来帮助他实现建造 /** * @author: 周靖松 * @information: 设计图抽象类 * @Date: 2019-07-14 10:11:10 */ class Diagram { constructor() { console.log('拿到图纸') } build(partName) { console.log(`观察${partName}图纸`); } } /** * @author: 周靖松 * @information: 设计图实现 * @Date: 2019-07-14 10:11:25 */ class CreatDiagram extends Diagram { constructor() { super(); } build(partName) { super.build(partName); console.log(`建造开始${partName}`); this.worker = new worker(partName); } getResult() { console.log('完工'); return this.worker; } } ok 设计图到手,开始招人干活 ...

July 14, 2019 · 2 min · jiezi

JavaScript设计模式七命令模式

命令模式:请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。生活小栗子:客户下单,订单记录了客户购买的产品,仓库根据订单给客户备货。 模式特点命令模式由三种角色构成: 发布者 invoker(发出命令,调用命令对象,不知道如何执行与谁执行);接收者 receiver (提供对应接口处理请求,不知道谁发起请求);命令对象 command(接收命令,调用接收者对应接口处理发布者的请求)。 发布者 invoker 和接收者 receiver 各自独立,将请求封装成命令对象 command ,请求的具体执行由命令对象 command 调用接收者 receiver 对应接口执行。 命令对象 command 充当发布者 invoker 与接收者 receiver 之间的连接桥梁(中间对象介入)。实现发布者与接收之间的解耦,对比过程化请求调用,命令对象 command 拥有更长的生命周期,接收者 receiver 属性方法被封装在命令对象 command 属性中,使得程序执行时可任意时刻调用接收者对象 receiver 。因此 command 可对请求进行进一步管控处理,如实现延时、预定、排队、撤销等功能。 代码实现class Receiver { // 接收者类 execute() { console.log('接收者执行请求'); }}class Command { // 命令对象类 constructor(receiver) { this.receiver = receiver; } execute () { // 调用接收者对应接口执行 console.log('命令对象->接收者->对应接口执行'); this.receiver.execute(); }}class Invoker { // 发布者类 constructor(command) { this.command = command; } invoke() { // 发布请求,调用命令对象 console.log('发布者发布请求'); this.command.cmd(); }}const warehouse = new Receiver(); // 仓库const order = new Command(warehouse); // 订单const client = new Invoker(order); // 客户client.invoke();/*输出: 发布者发布请求 命令对象->接收者->对应接口执行 接收者执行请求*/应用场景有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。需要一种松耦合的方式来设计程序,使得发送者和接收者能够消除彼此之间的耦合关系。——《JavaScript 设计模式与开发实践》不关注执行者,不关注执行过程;只要结果,支持撤销请求、延后处理、日志记录等。优缺点优点: ...

July 14, 2019 · 1 min · jiezi

设计模式之装饰者模式

0x01.定义与类型定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。特点: 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。装饰对象包含一个真实对象的引用(reference)装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。类型:结构型UML类图 样例实现/*** 组件类接口*/public interface IComponent { void operation();}/*** 具体组件类的具体业务逻辑实现*/public class Component implements IComponent { @Override public void operation() { System.out.println("component operation!"); }}/*** 装饰器的抽象类*/public abstract class ADecorator implements IComponent { /** * 关键在于这个组合组件接口对象 */ private IComponent component; public ADecorator(IComponent component) { this.component = component; } @Override public void operation () { component.operation(); }}/*** 装饰器具体实现1*/public class Decorator1 extends ADecorator { public Decorator1(IComponent component) { super(component); } @Override public void operation() { super.operation(); System.out.println("decorator1"); }}/*** 装饰器具体实现2*/public class Decorator2 extends ADecorator { public Decorator2(IComponent component) { super(component); } @Override public void operation() { super.operation(); System.out.println("decorator2"); }}测试与应用类/*** 应用与测试类*/public class Test { public static void main(String[] args) { //应用类 IComponent component; //初始化 component = new Component(); //装饰 component = new Decorator1(component); component = new Decorator2(component); //具体方法的调用 component.operation(); }}输出结果component operation!decorator1decorator2装饰着模式中的各组件: ...

July 13, 2019 · 2 min · jiezi

设计模式之门面模式

0x01.定义与类型定义:又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口外观模式定义了一个高层接口,让子系统更容易使用类型:结构型UML类图 门面模式是对系统复杂的关系处理做了一个封装,对外提供一个简单的接口,成员介绍: 子系统:被门面模式封装的子系统,也是具体业务逻辑的细节facade类:门面类,对子系统执行流程进行封装,对外开放功能接口,一般为单例对象。0x02.适用场景子系统越来越复杂,增加外观模式提供简单调用接口构建多层系统结构,利用外观对象作为每层的入口,简化层间调用0x03.优点简化了调用过程,无需了解深入子系统,防止带来风险减少系统依赖、松散耦合更好的划分访问层次符合迪米特法则,即最少知道原则0x04.缺点增加子系统,需要修改门面类,容易引入风险。修改门面类,不符合开闭原则0x05.样例代码场景:假设积分兑换物品流程,一共有三部分别依赖三个子系统1.积分校验系统,查看是否有资格。2.积分支付系统,兑换礼物,扣减积分等。3.物流系统,兑换礼物后,进行配送流程。如果不适用门面模式,需要在客户端进行三个步骤的调用,而门面封装后只需要使用门面类,下面具体代码实现:/** * 礼物实体类 */public class PointsGift { private String name; public PointsGift(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }}/** * 支付子系统 */public class PointsPaymentService { public boolean pay(PointsGift pointsGift) { //扣减积分 System.out.println("支付:" + pointsGift.getName() + " 积分成功!"); return true; }}/** * 积分校验子系统 */public class QualifyService { public boolean isAvailable (PointsGift pointsGift) { System.out.println("校验" + pointsGift.getName() + "积分资格通过,库存通过"); return true; }}/** * 物流子系统 */public class ShippingService { public String shipGift (PointsGift pointsGift) { //物流系统的对接逻辑 System.out.println(pointsGift.getName() + "进入物流系统"); return "666"; }}/** * 扣减积分门面类 */public class GiftExchangeService { /** * 模拟注入 */ private QualifyService qualifyService = new QualifyService(); private PointsPaymentService pointsPaymentService = new PointsPaymentService(); private ShippingService shippingService = new ShippingService(); //模拟注入,一开始就已经有了三个依赖的子系统// public void setQualifyService(QualifyService qualifyService) {// this.qualifyService = qualifyService;// }//// public void setPointsPaymentService(PointsPaymentService pointsPaymentService) {// this.pointsPaymentService = pointsPaymentService;// }//// public void setShippingService(ShippingService shippingService) {// this.shippingService = shippingService;// } public void giftExchange (PointsGift pointsGift) { if (qualifyService.isAvailable(pointsGift)) { //资格校验通过 if (pointsPaymentService.pay(pointsGift)) { //如果支付积分成功 String shippingOrderNo = shippingService.shipGift(pointsGift); System.out.println("物流订单号:" + shippingOrderNo); } } }}测试与调用类/** * 客户端与测试类 */public class Test { public static void main(String[] args) { PointsGift pointsGift = new PointsGift("连衣裙"); GiftExchangeService giftExchangeService = new GiftExchangeService();// giftExchangeService.setQualifyService(new QualifyService());// giftExchangeService.setPointsPaymentService(new PointsPaymentService());// giftExchangeService.setShippingService(new ShippingService()); giftExchangeService.giftExchange(pointsGift); }}测试输出结果:校验连衣裙积分资格通过,库存通过支付:连衣裙 积分成功!连衣裙进入物流系统物流订单号:666样例UML ...

July 12, 2019 · 2 min · jiezi

设计模式学习笔记工厂模式

一、概念1、工厂模式分为三类:(1)简单工厂模式:一个工厂创建所有产品(2)工厂方法模式:一个工厂方法创建一个产品(3)抽象工厂模式:一个工厂方法创建一类产品 2、创建型模式这种模式是指将实例的创建和使用分离开来,隐藏类实例的创建细节,外界对于对象只需要知道它们共同的接口即可。 二、简单工厂模式1、组成(1)工厂类:工厂模式的核心,通过调用它来实现其他类的功能。(2)抽象产品角色(3)具体产品角色:继承抽象工厂角色,工厂类就是创建此角色的实例。 2、举例想要说明这个模式,我们可以看一个简单的例子(1)首先创建一个Operation抽象类,作为抽象产品角色 public abstract class Operation { private int operator1; private int operator2; public int getOperator1() { return operator1; } public int getOperator2() { return operator2; } public void setOperator1(int operator1) { this.operator1 = operator1; } public void setOperator2(int operator2) { this.operator2 = operator2; } protected abstract int getResult();}(2)如果我们想要实现加减法运算,可以写一个类继承Operation类,并且重写getResult方法。 public class OperationAdd extends Operation { @Override public int getResult(){ return getOperator1() + getOperator2(); }}public class OperationMinus extends Operation { @Override public int getResult(){ return getOperator1() - getOperator2(); }}(3)如果我们要进行加减法运算,可以直接调用这两个类创建出的实例的方法,但是简单工厂模式希望我们能够另外建立一个工厂,并且用这个工厂来创建加减法类的实例。因为这样我们就不需要知道具体类的名字,只需要传入加减法的符号即可。 ...

July 12, 2019 · 1 min · jiezi

好久不见Java设计模式

引子设计模式是很多程序员总结出来的最佳实践。曾经在刚开始写项目的时候学习过设计模式,在开发过程中,也主动或者被动的使用过。现在写代码虽说不会特意明确在用哪种设计模式,但潜移默化的写出来公认的最佳实践代码,毕竟看的比较清爽。为什么再看一遍设计模式,主要有几个原因:第一,很多优秀的源码基本都使用了设计模式,明确设计模式能够更好的看源码。第二,很多中间件设计理念也是基于设计模式的,还有其他的语言,都有自己的设计最佳实践。对于我来说,设计模式始于java,不止于java。第三,有了这种规范,可以更好的和他人沟通,言简意赅。 设计模式原则很多优秀的文章和书籍都讲的很明白了,我说下自己的体会。1.单一职责原则,就是一个类只负责做一件事情。这样就可以做到解耦合的效果,让代码看起来比较清爽,也体现了java的封装性。还有个原则叫迪米特法则,就是一个对象对另一个对象有尽量少的了解,说的也是解耦合的事情。2.里氏替换原则和依赖导致原则,说的是继承的事情。父类可以做的事情,子类都可以去做,子类可以尽量去依赖父类去做事情;但是反过来,父类不能依赖子类去做一些事情。体现了java的继承特性。3.接口隔离原则,接口也应该尽可能的隔离开来。其实类写多了,的确耦合性低,为了让他们交流起来,用的最多的就是接口,毕竟只需要知道做什么,怎么做,去访问那个具体的类吧。4.开闭原则,对修改关闭,对拓展开放。就是代码需要有很好的延展性,对原有代码结构不能破坏。 创建者模式创建者模式就是为了用优雅的方式创建我们使用的类。 1.简单工厂模式这个用的比较少,就是有个工厂,告诉你我要什么东西,你造好了给我就行。比如说: public interface Ball { public String create();}public class Soccer implements Ball { @Override public String create() { return "give you a soccer"; }}public class BasketBall implements Ball { @Override public String create() { return "give you a basketBall"; }}public class EasyBallFactory { public static Ball createBall(String name){ if (name.equals("basketball")){ return new BasketBall(); }else if(name.equals("soccer")){ return new Soccer(); }else { return null; } } public static void main(String[] args) { Ball soccer = EasyBallFactory.createBall("soccer"); Ball basketBall = EasyBallFactory.createBall("basketball"); System.out.println(soccer.create()); //give you a soccer System.out.println(basketBall.create()); //give you a basketBall }}2.工厂模式这个其实和简单工厂模式差不太多,就是将工厂继续拆分,比如说刚刚EasyBallFactory是一个总工厂,我们现在拆分成SoccerFactory和BasketBallFactory分别生产足球和篮球。某个工厂内部可以根据需求生产不同的产品,比如说soccer可以生产不同大小的出来。 ...

July 11, 2019 · 6 min · jiezi

js常用设计模式实现二工厂模式和抽象工厂模式

创建型模式创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离上一篇介绍了下单例模式,这一篇介绍一下工厂模式和抽象工厂模式,为什么把这两个放在一起说,因为我个人认为他们两个是有关联的,简单工厂模式被我看作是工厂模式的简洁版,然后工厂模式被我看作是抽象工厂模式的简洁版 简单工厂定义:简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。比如你去专门卖鼠标的地方你可以买各种各样的鼠标 最通俗的理解就是封装,简单带过一下 function mouse(color,long){ var o = new Object(); o.color= color; o.long= long; o.Explain= function(){ console.log(this.color,this.long); } return o; } var mouseA = mouse('黑色',3);工厂模式定义:工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。比如去旁边还有个专门卖键盘的和耳机的然后他和卖鼠标的组成了一个门店 你要进如这个门店然后买鼠标或者键盘或者耳机 你可以把这个工厂模式理解成是 进化版的简单工厂模式 shop= function(val) { if(typeof this === "object") { //看看有没有new 没有new的话new一个 var s = new this[val](); return s; } else { return new shop(val); } } shop.prototype = { mouse: function() { console.log("买了个鼠标") }, keyboard: function() { console.log("买了个键盘") }, headset: function() { console.log("买了个耳机") } } bigShop = new shop(); mouse = new bigshop.mouse();抽象工厂模式定义:抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。前边的两个都是直接生产实例的,开始以为抽象工厂也是但是后来发现抽象工厂更像是生产工厂,其实抽象工厂其实是实现子类继承父类的方法。 ...

July 11, 2019 · 2 min · jiezi

js常用设计模式实现一单例模式

什么是设计模式设计模式是一种能够被反复使用,符合面向对象特性的代码设计经验的总结,合理的使用设计模式能够让你得代码更容易维护和可靠设计模式的类型共分为创建型模式,结构型模式,行为型模式三种 创建型模式创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离,创建模式有 单例模式抽象工厂模式建造者模式工厂模式原型模式单例模式单例模式的定义是保证一个类仅有一个实例,单例模式它必须自行创建这个实例,并提供一个访问他的全局的访问点es5的实现var only = function(data) { this.data = data; this.Instance = null;}only.go = function(data) { if(!this.Instance) { this.Instance = new only(data); } return this.Instance;}let obj1 = only.go('1')let obj2 = only.go('2')console.log(obj1 === obj2);console.log(obj1);console.log(obj2);es6class only { constructor(data) { if (only.prototype.Instance === undefined) { this.data = data; only.prototype.Instance = this; } return only.prototype.Instance; }}let ob1 = new only("a");let ob2 = new only("b");ob2.init = 'init';console.log(ob1 === ob2);console.log(ob1);console.log(ob2);上边的代码中,无论怎么new,其结果都是唯一的那个实例 单例模式的优缺点单例模式,因为他的实例是唯一的,所以完全可以通过创建的时候,严格的去控制怎么去创建和访问或者说抛出错误,如果存在频繁的创建和销毁的操作的时候,单例模式事可以提高性能的 但是同样的,单纯的单例模式中是没有抽象操作的,所以说单例模式是一个不便于扩展的模式 ...

July 11, 2019 · 1 min · jiezi

开篇二十三种设计模式的通俗理解

本文为本次系列文章的第一篇,接下来,小编预计用一周的时间,带大家重新解读二十三中设计模式,如果你觉得本文对你有帮助的话,可以帮小编点一下“关注”以及“转发”,支持一下小编,谢谢!一、设计模式的分类总体来说设计模式分为三大类: 创建型模式,共五种: 工厂方法模式抽象工厂模式单例模式建造者模式原型模式。结构型模式,共七种: 适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式。行为型模式,共十一种: 策略模式模板方法模式观察者模式迭代子模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式。其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下: 二、设计模式的六大原则总原则:开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。 1、单一职责原则 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。 2、里氏替换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。 4、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。 5、迪米特法则(最少知道原则)(Demeter Principle) 就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。 最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。 6、合成复用原则(Composite Reuse Principle) 原则是尽量首先使用合成/聚合的方式,而不是使用继承。 三、最后本文作为本次系列文章的开篇,暂时讲到这里,从下一篇开始,我将详细介绍Java种23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析。 对本系列内容感兴趣的同学可以帮小编点一下“关注”,支持一下小编,谢谢!

July 11, 2019 · 1 min · jiezi

设计模式学习笔记单例模式

一、概念一般采用单例模式就是为了满足“只创建一个类”的需要。 单例模式有两个特点:(1)一个类仅有一个实例(2)仅有一个全局访问点 二、写法1、饿汉式所谓饿汉式,是指这个类的实例在类加载阶段就已经创建出来了。 public class Singleton1 { //在类的内部实例化一个实例 private static final Singleton1 instance = new Singleton1(); //隐藏构造方法 private Singleton1() { } //对外开放一个获取实例的方法 public static Singleton1 getInstance(){ return instance; }}当然也可以使用static代码块。(注意这里的final,看网上有各种写法,加或者不加的都有,我认为这里可以加,这样getInstance方法里就不需要判空了) 这种方式虽然能够有效避免线程安全的问题,但是却可能造成不必要的浪费,因为也许这个实例并不需要用到,或者就用到一两次,却要一直占用内存。 2、静态内部类 public class InnerClassSingleton { //在静态内部类中实例化对象 private static class SingletonHolder{ private static final InnerClassSingleton instance = new InnerClassSingleton(); } //隐藏构造方法 private InnerClassSingleton(){ } //对外提供访问方法 public static final InnerClassSingleton getInstance() { return SingletonHolder.instance; }}3、懒汉式所谓懒汉式,就是在使用时才加载,比较“懒一些”。 public class Singleton2 { private static Singleton2 instance; private Singleton2(){ } public static Singleton2 getInstance(){ //在对象被使用时才实例化 if (instance == null) instance = new Singleton2(); return instance; }}注意:加上synchronized关键字就可以保证线程安全。 ...

July 11, 2019 · 1 min · jiezi

JavaScript-设计模式六观察者模式与发布订阅模式

观察者模式(Observer)观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。简单点:女神有男朋友了,朋友圈晒个图,甜蜜宣言 “老娘成功脱单,希望你们欢喜”。各位潜藏备胎纷纷失恋,只能安慰自己你不是唯一一个。 模式特征一个目标者对象 Subject,拥有方法:添加 / 删除 / 通知 Observer;多个观察者对象 Observer,拥有方法:接收 Subject 状态变更通知并处理;目标对象 Subject 状态变更时,通知所有 Observer。Subject 添加一系列 Observer, Subject 负责维护与这些 Observer 之间的联系,“你对我有兴趣,我更新就会通知你”。 代码实现// 目标者类class Subject { constructor() { this.observers = []; // 观察者列表 } // 添加 add(observer) { this.observers.push(observer); } // 删除 remove(observer) { let idx = this.observers.findIndex(item => item === observer); idx > -1 && this.observers.splice(idx, 1); } // 通知 notify() { for (let observer of this.observers) { observer.update(); } }}// 观察者类class Observer { constructor(name) { this.name = name; } // 目标对象更新时触发的回调 update() { console.log(`目标者通知我更新了,我是:${this.name}`); }}// 实例化目标者let subject = new Subject();// 实例化两个观察者let obs1 = new Observer('前端开发者');let obs2 = new Observer('后端开发者');// 向目标者添加观察者subject.add(obs1);subject.add(obs2);// 目标者通知更新subject.notify(); // 输出:// 目标者通知我更新了,我是前端开发者// 目标者通知我更新了,我是后端开发者优势目标者与观察者,功能耦合度降低,专注自身功能逻辑;观察者被动接收更新,时间上解耦,实时接收目标者更新状态。不完美观察者模式虽然实现了对象间依赖关系的低耦合,但却不能对事件通知进行细分管控,如 “筛选通知”,“指定主题事件通知” 。 ...

July 10, 2019 · 2 min · jiezi

设计模式学习笔记适配器模式

一、结构型设计模式1、概念结构型设计模式描述如何将类或者对象结合在一起形成更为复杂,功能更为强大的结构。 2、分类(1)类结构模型:这种结构模型关心类的组合,类直接组合成一个更大的系统,通常用继承来建立这种关系。(2)对象结构模型:这种模型结构关心类与对象的组合,通常在一个类中使用另一个类的对象,再调用其方法。 注:“合成复用原则”规定尽量用关联关系来代替继承关系,因此一般使用对象结构模型。 二、适配器模式1、概念适配器模式是一种结构型模式,它的思想是将一个接口转化为另一个接口。适配器模式包含四个角色:(1)对象:定义所需要的方法(2)请求者:负责使用对象定义的方法来做具体的处理(3)被适配者:以持有方法的角色(4)适配器:使用被适配者的方法来满足对象的需要。 2、代码示例:使用安卓充电器给苹果充电(1)安卓和苹果充电器接口 //安卓public interface MicroUsbInterface { public void chargeWithMicroUsb();}//苹果public interface LightningInterface { public void chargeWithLightning();}(2)安卓设备充电器 public class AndroidCharger implements MicroUsbInterface { @Override public void chargeWithMicroUsb() { System.out.println("使用MicroUsb型号的充电器充电..."); }}(3)适配器(用安卓充电器适配苹果充电器) public class Adapter implements LightningInterface { //将安卓充电器的接口作为一个字段 private MicroUsbInterface microUsbInterface; public Adapter() { } public Adapter(MicroUsbInterface microUsbInterface) { this.microUsbInterface = microUsbInterface; } @Override public void chargeWithLightning() { //使用安卓充电器给苹果设备充电 microUsbInterface.chargeWithMicroUsb(); } public MicroUsbInterface getMicroUsbInterface() { return microUsbInterface; } public void setMicroUsbInterface(MicroUsbInterface microUsbInterface) { this.microUsbInterface = microUsbInterface; }}在这个示例中,对象就是给苹果手机充电,请求者就是苹果手机,被适配者是安卓充电器,适配器是用安卓充电器给苹果手机充电的适配器。 ...

July 8, 2019 · 2 min · jiezi

JavaScript设计原则编程技巧及常用设计模式

【前言】最近阅读量了《JavaScript设计模式与开发实践》,收货颇丰,于是想写一点总结及感想 写一篇文章对于我的意义在于: 给别人讲述知识时可以发现自己掌握的是否牢固透彻,写的过程不断发现自己的不足,然后通过一些方式来解决问题,这也是一种学习过程;当然,写文章给别人看,也要从读者的角度出发,考虑他们想要从这篇文章获得什么,还有就是你想表达些什么给读者这种过程大概叫费曼学习法,图解:(图片来自网络,侵删) 这篇文章我想表达的是:学习设计原则设计模式的好处、介绍设计原则和设计模式、常用设计模式的实践、代码重构的具体方法、一些问题一些思考。你可以先读一遍带着疑问去阅读这本书籍或者阅读完书籍再来看这篇文章是否有助于你理解 一、为什么要学习设计原则、设计模式首先,设计原则、设计模式受用的目标人群我觉得是有一定的js基础且有一定的项目实践经历的开发者,不然的话,就算学习设计也是生搬硬套,收货甚微,当有了一定基础及实践之后,阅读本书之后有三种感觉: 你的某些代码就是书上的反例,醍醐灌顶的感觉你的某些代码已经实践了某些设计模式遵从了某些设计原则,但是你并不知道这样写代码是叫这个模式以及这个模式的全部优缺点或者你的代码还有更进一步优化的空间内心冷笑一声:哼 so easy... emmmmm,如果大佬还愿意继续阅读本文的话,希望大佬可以在评论区指点一二个人认为,JavaScript设计原则以及设计模式都只属于软件设计的一部分,但这意味着已经开始脱离了’API调用工程师‘的称号,开始接触编程思想,但是设计原则跟模式有限,只针对于代码层面。 打个比方:vue源码使用了xx模式,解决了xx问题,但是,在选择xx模式解决xx问题的背后又有多少思考????呢?简单猜测一下:一个框架or库 = 软件设计 + 其他框架优点借鉴 + 创新 + 编码 + 测试;本人水平有限,这是知识宏观的揣测一下实现一个框架or库需要的付出。请不要当真or较真 可能没人要你去写框架什么的,但是你负责的部分总是你来写来维护,不按套路出牌受伤的是自己举这个例子是想说明:学习一下设计原则设计模式是多么的有必要(强行解释,最为致命) 二、JavaScript常用设计原则我想提一个这本书的缺点,就是目录,设计模式都是要遵循设计原则的,而且很多设计模式章节都提到了设计原则,然而书的目录是最后一个大章节才说的设计原则,我觉得设计原则应该放在设计模式之前,所以建议先阅读设计原则再阅读设计模式,在理解上也会有帮助,至于第一章节的基础知识介绍,这个看各人情况可以选择要不要忽略 下面介绍常用的设计原则 1. 单一职责原则 SRP【定义】 单一职责原则的定义是‘引起变化的原因’,如果你有两个原因去修改一个方法,说明这个方法有两个职责,承担的职责越多你需要修改他的可能性就越大,修改代码总是件危险的事情,特别是两个职责耦合在一起的时候一句话概括:一个对象(方法)只做一件事【理解】 书上有介绍单一职责原则相关的模式,其实我觉得从原则去联系模式有点不合理,因为所有模式都会去遵从设计原则,知识侧重点不一样而已。所以我会在下个章节的模式里去联系原则,而说原则,我只想脱离模式单独去说原则的优缺点以及如何应用【优点】降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。【缺点】增加编码复杂度,同时增加对象之间联系的难度 【应用】煮个栗子,js的组件化开发,有一种思想就是组件的原子化,把组件拆到不能再拆的粒度,是最好维护的。但事实真的是这样吗,组件多了,组件之间的联系也多了,就意味需要管理更多的组件及组件间的复杂关系,有时候明明两个组件像孪生兄弟一样分不开,你偏要拆散他们并且给他们一座桥梁去联系,这无疑比让他们在一起成为一个整体要难维护的多,而且颗粒度越小,代表着开发成本越大。 单一职责原则也是如此,这些思想其实建立在理想化的状态上,而实际情况往往不会与理想对等,所以实际项目使用中,要学会灵活多变,知道什么时候其实是可以违反原则的,借用书上的话: SRP 原则是所有原则中最简单也是最难正确运用的原则之一。要明确的是,并不是所有的职责都应该一一分离 一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。比如在 ajax 请求的时候,创建 xhr 对象和发送 xhr 请求几乎总是在一起的,那么创建 xhr 对象的职责和发送 xhr 请求的职责就没有必要分开。 另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦 合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的 时候再进行分离也不迟 个人认为这是理解最简单,实践最难的一个 // badvar createLoginLayer = (function(){ var div; return function(){ if ( !div ){ div = document.createElement( 'div' ); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild( div ); } return div; }})();// goodvar getSingle = function( fn ){ // 获取单例 var result; return function(){ return result || ( result = fn .apply(this, arguments ) ); }var createLoginLayer = function(){ // 创建登录浮窗 var div = document.createElement( 'div' ); div.innerHTML = '我是登录浮窗'; document.body.appendChild( div ); return div; };var createSingleLoginLayer = getSingle( createLoginLayer );var loginLayer1 = createSingleLoginLayer();var loginLayer2 = createSingleLoginLayer();alert ( loginLayer1 === loginLayer2 ); // 输出: true2. 最少知识原则 LKP单一职责原则说道:一个对象(方法)只做一件事;那代表着我们要创建更多的对象(方法)来分解一个之前比较大的对象(方法),那分解之后,对象(方法)是小了,好维护好扩展了,但是对象之间的联系缺越来越多了,有两个对象非常耦合,怎么办 ...

July 4, 2019 · 4 min · jiezi

JavaScript-设计模式五迭代器模式

文章内容分两部分: 前半部分为 “迭代器模式” 概念;后半部分为 ES6 中 Iterator (迭代器)上半部分开始... 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)。 生活小栗子:清单 TodoList。每日清单有学习类、生活类、工作类、运动类等项目,清单列表只管罗列,不管类别。 模式特点为遍历不同数据结构的 “集合” 提供统一的接口;能遍历访问 “集合” 数据中的项,不关心项的数据结构模式实现// 统一遍历接口实现var each = function(arr, callBack) { for (let i = 0, len = arr.length; i < len; i++) { // 将值,索引返回给回调函数callBack处理 if (callBack(i, arr[i]) === false) { break; // 中止迭代器,跳出循环 } }}// 外部调用each([1, 2, 3, 4, 5], function(index, value) { if (value > 3) { return false; // 返回false中止each } console.log([index, value]);})// 输出:[0, 1] [1, 2] [2, 3]“迭代器模式的核心,就是实现统一遍历接口。” ...

July 1, 2019 · 3 min · jiezi

Java设计模式策略模式

定义Define a family of algorithms,encapsulate each one,and make them interchangeable.定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。 策略模式使用的就是面向对象的继承和多态机制,非常容易理解和掌握 实现抽象策略策略、算法家族的抽象,通常为接口,也可以是抽象类,定义每个策略或算法必须具有的方法和属性。 public interface Strategy { /** * 策略模式的运算法则 */ void doSomething();}具体策略实现抽象策略中的操作,该类含有具体的算法 public class ConcreteStrategyA implements Strategy { @Override public void doSomething() { System.out.println("具体策略A的运算法则"); }}public class ConcreteStrategyB implements Strategy { @Override public void doSomething() { System.out.println("具体策略B的运算法则"); }}封装类也叫做上下文类或环境类,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。 策略模式的重点就是封装角色,它是借用了代理模式的思路,和代理模式的差别就是策略模式的封装角色和被封装的策略类不用是同一个接口,如果是同一个接口那就成为了代理模式。 public class Context { /** * 抽象策略 */ private Strategy strategy; /** * 构造函数设置具体策略 * * @param strategy */ public Context(Strategy strategy) { this.strategy = strategy; } /** * 封装后的策略方法 */ public void executeStrategy() { this.strategy.doSomething(); }}客户端代码public class Client { public static void main(String[] args) { // 声明一个具体的策略 Strategy strategyA = new ConcreteStrategyA(); // 声明上下文对象 Context contextA = new Context(strategyA); // 执行封装后的方法 contextA.executeStrategy(); Strategy strategyB = new ConcreteStrategyB(); Context contextB = new Context(strategyB); contextB.executeStrategy(); }}优点算法可以自由切换这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。 ...

July 1, 2019 · 2 min · jiezi

分布式系统设计模式

layout: posttitle: 分布式系统设计模式category: 技术keywords: 架构,分布式,微服务,后端,分布式系统,设计模式 comments: true概述这篇文章是对于【分布式系统设计模式】的个人理解和部分翻译。 文章探讨了关于《基于容器化软件组件的微服务架构》。 其实容器化编程的发展路径和面向对象编程有异曲同工之妙--都是将复杂的系统进行抽象、解耦,然后通过一定的方式组合起来。 既然我们要组合,肯定会有面对不同情况的不同组合方式。所以,这些不同的组合方式也会有几个常用的固定模式。而这个正式我们要探讨的--分布式系统设计模式。 说到分布式,第一个联想到的应该就的容器化。为什么?其实容器化和分布式本没有交集,只是因为我们发现容器化是一个实现分布式的高效的方法。 容器化设置了一个天然的边界,边界之外用接口进行通信。有了这个边界的好处就是,任何意料之外的情况都可以被限制在最小的影响范围,毕竟我们构建的是一个大型的复杂系统。 我认为,用FMEA模型能很好的描述为什么会采用容器化去解构分布式系统。(FMEA,可以理解为:失控的状态一定会发生,我们要做的是控制失控的范围) 所以,我们接下来要说的设计模式基本上都是和容器相关,我们需要把容器作为一等公民去看。毕竟这是写 Kubernetes 的哥们写的。 单容器管理者模式 (Single-container management patterns)我们为容器增加一些可控接口,比如 run(), stop(), pause(),使得容器对外来说是可控的。 也正是因为广泛的 http 协议支持,你完全可以通过 http 和 JSON这样的序列化方式去构造你应用的对外的 API。 一般来说我们的设计方针都是一个容器提供一种服务。同时容器会为其上下游提供接口。 什么接口? 向上,提供容器本身丰富的信息接口。能够为特定的监控容器运行状态的应用提供信息。 向下,提供了控制容器本身的接口。使得分布式系统能够更有效的管理整个应用的生命周期,以及应用的优先级。 比如,一个集群的管理系统,能够设置集群处理任务的优先级。(比如 K8s 中的抢占式调度) 所以一旦采用这种模式的话,你会需要一个统一的管理平台,通过接口去管理(组管理)单个容器。 单节点-多容器应用模式 (Single-node, multi-container application patterns)这种模式比较好理解,有些容器应用是需要“共生”的,所以我们会将其放在同一个节点上。一旦这样,分布式系统会对容器组做联合调度。比如 K8s 里将调度单位描述成了 Pods(一个 Pod 可能包含多个容器),Nomad 则称其为 task groups。下面几个就是常用的单节点多容器的设计模式: 副载模式(Sidecar pattern)多容器部署最通用的一种模式,就是 sidecar 模式。其实大家都直接称呼 Sidecar 模式,不会翻译成副载。 那 Sidecar 是个啥样子呢? 举个例子吧:我们有一个主容器是 Web Server,我们需要收集 Web Server 所产生的日志。所以我们会有一个专门的 sidecar 容器,负责收集日志并把日志存储到集群的存储系统。 ...

June 27, 2019 · 1 min · jiezi

依赖和耦合

我刚开始学习软件工程,学习设计模式,总是会聊到设计模式,总是会说高内聚,低耦合,还有依赖注入。什么是依赖,什么是耦合?现在来聊一聊我对于依赖和耦合的理解。 先看下面一段简单代码, class Apple{ private String name = "Apple";}class People{ private List<Apple> stomach = new ArrayList<>(); public void eat(Apple apple){ stomach.add(apple); }}class MainDemo { public static void main(String[] args) { People xiaoming= new People(); Apple apple = new Apple(); xiaoming.eat(apple); }}一个人类,一个苹果类,代码的意思是小明吃苹果,保存在胃中。现在又有一个需求:小明想要去吃香蕉,想要去吃梨子。用上面这一段的代码就不好改了。要完成需求就需要做大量的变化。再加一个 Banana stomach?这个需求是一个合理的需求,但是因为我们这一段的代码是不合理的,产生了依赖和耦合。 如何减少依赖,就是使用面向接口的编程。依赖一个抽象而不是依赖一个实现。 interface Food{ }class Banana implement Food{ private String name = "Banana";}class Apple implement Food{ private String name = "Apple";}class People{ private List<Apple> stomach = new ArrayList<>(); public void eat(Food food){ stomach.add(food); }}class MainDemo { public static void main(String[] args) { People xiaoming= new People(); Food apple = new Apple(); xiaoming.eat(apple); Food banana = new Banana(); xiaoming.eat(banana); }}这样其实我的接口里面啥也没有,但是却将依赖转移到了抽象的接口。这样我每次添加都可以增加不同水果。其实我这个取名是Food,那么这个其实可以添加其他的种类食物。这也体现了开放-闭合原则。再看最后一个例子 ...

June 27, 2019 · 2 min · jiezi

JavaScript-设计模式四适配者模式

适配器模式:将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),使得原本由于接口不兼容而不能一起工作的那些类(对象)可以正常协作。简单理解就是为兼容而生的 “转换器”。对于强迫症患者,那么多形状各异的接口,对应着不同数据线。如果可以有一个转接口实现集合兼容,岂不美哉。没错,这样一个适配器,你值得拥有。(各大厂商加把劲。。。) 生活小栗子: 港式插头转换器笔记本电源适配器Type-C 转接口模式特点新包装对象(适配器对象)实现对原对象接口的访问(接口名可不同)适配器对象不对请求数据做预处理,直接传入原对象接口处理适配器对象对外接口名统一,外部调用者可统一接口调用多对象方法模式实现实现方式:在不改变原有对象接口的基础上,定义一个包装对象,新对象转接调用原有接口,使外部调用者可以正常使用。第三方SDK的应用// 适配器实现地图SDK统一渲染var googleMap = { show: function(){ console.log('开始渲染谷歌地图'); }};var baiduMap = { display: function(){ console.log('开始渲染百度地图'); }};// 外部实际调用的适配器对象var baiduMapAdapter = { show: function() { return baiduMap.display(); }};// 外部调用者var renderMap = function(map) { map.show(); // 统一接口调用};renderMap(googleMap);renderMap(baiduMapAdapter);ES6实现适配器// 使用ES6改写适配器实现地图SDK统一渲染class googleMap { show() { console.log('开始渲染谷歌地图'); }}class baiduMap { display() { console.log('开始渲染百度地图'); }}class baiduMapAdapter extends baiduMap { constructor() { super(); } show() { this.display(); }}// 外部调用者function renderMap(map) { map.show(); // 统一接口调用}renderMap(new googleMap());renderMap(new baiduMapAdapter());jQuery中的应用适配器模式非常适用于跨浏览器兼容,例如强大的 jQuery 封装了事件处理的适配器,解决跨浏览器兼容性问题,极大简化我们日常编程操作。 ...

June 26, 2019 · 1 min · jiezi

JavaScript-设计模式三代理模式

代理模式:为一个对象提供一个代用品或占位符,以便控制它的访问。当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问。替身对象可对请求预先进行处理,再决定是否转交给本体对象。 生活小栗子: 代购;明星经纪人;和谐上网经常 shopping 的同学,对代购应该不陌生。自己不方便直接购买或买不到某件商品时,会选择委托给第三方,让代购或黄牛去做购买动作。程序世界的代理者也是如此,我们不直接操作原有对象,而是委托代理者去进行。代理者的作用,就是对我们的请求预先进行处理或转接给实际对象。 模式特点代理对象可预先处理请求,再决定是否转交给本体;代理和本体对外显示接口保持一致性代理对象仅对本体做一次包装模式细分虚拟代理(将开销大的运算延迟到需要时执行)缓存代理(为开销大的运算结果提供缓存)保护代理(黑白双簧,代理充当黑脸,拦截非分要求)防火墙代理(控制网络资源的访问)远程代理(为一个对象在不同的地址控件提供局部代表)智能引用代理(访问对象执行一些附加操作)写时复制代理(延迟对象复制过程,对象需要真正修改时才进行)JavaScript 中常用的代理模式为 “虚拟代理” 和 “缓存代理”。 模式实现实现方式:创建一个代理对象,代理对象可预先对请求进行处理,再决定是否转交给本体,代理和本体对外接口保持一致性(接口名相同)。// 例子:代理接听电话,实现拦截黑名单var backPhoneList = ['189XXXXX140']; // 黑名单列表// 代理var ProxyAcceptPhone = function(phone) { // 预处理 console.log('电话正在接入...'); if (backPhoneList.includes(phone)) { // 屏蔽 console.log('屏蔽黑名单电话'); } else { // 转接 AcceptPhone.call(this, phone); }}// 本体var AcceptPhone = function(phone) { console.log('接听电话:', phone);};// 外部调用代理ProxyAcceptPhone('189XXXXX140'); ProxyAcceptPhone('189XXXXX141'); 代理并不会改变本体对象,遵循 “单一职责原则”,即 “自扫门前雪,各找各家”。不同对象承担独立职责,不过于紧密耦合,具体执行功能还是本体对象,只是引入代理可以选择性地预先处理请求。例如上述代码中,我们向 “接听电话功能” 本体添加了一个屏蔽黑名单的功能(保护代理),预先处理电话接入请求。 虚拟代理(延迟执行)虚拟代理的目的,是将开销大的运算延迟到需要时再执行。 虚拟代理在图片预加载的应用,代码例子来至 《JavaScript 设计模式与开发实践》 // 本体var myImage = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } }})();// 代理var proxyImage = (function(){ var img = new Image; img.onload = function() { myImage.setSrc(this.src); // 图片加载完设置真实图片src } return { setSrc: function(src) { myImage.setSrc('./loading.gif'); // 预先设置图片src为loading图 img.src = src; } }})();// 外部调用proxyImage.setSrc('./product.png'); // 有loading图的图片预加载效果缓存代理(暂时存储)缓存代理的目的,是为一些开销大的运算结果提供暂时存储,以便下次调用时,参数与结果不变情况下,从缓存返回结果,而不是重新进行本体运算,减少本体调用次数。 ...

June 25, 2019 · 3 min · jiezi

你真的了解iOS代理设计模式吗

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/2113ffe54b30 在项目中我们经常会用到代理的设计模式,这是iOS中一种消息传递的方式,也可以通过这种方式来传递一些参数。这篇文章会涵盖代理的使用技巧和原理,以及代理的内存管理等方面的知识。我会通过这些方面的知识,带大家真正领略代理的奥妙。写的有点多,但都是干货,我能写下去,不知道你有没有耐心看下去。 本人能力有限,如果文章中有什么问题或没有讲到的点,请帮忙指出,十分感谢! iOS中消息传递方式在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式。 通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。代理:是一种通用的设计模式,iOS中对代理支持的很好,由代理对象、委托者、协议三部分组成。block:iOS4.0中引入的一种回调方法,可以将回调处理代码直接写在block代码块中,看起来逻辑清晰代码整齐。target action:通过将对象传递到另一个类中,在另一个类中将该对象当做target的方式,来调用该对象方法,从内存角度来说和代理类似。KVO:NSObject的Category-NSKeyValueObserving,通过属性监听的方式来监测某个值的变化,当值发生变化时调用KVO的回调方法。.....当然还有其他回调方式,这里只是简单的列举。 代理的基本使用代理是一种通用的设计模式,在iOS中对代理设计模式支持的很好,有特定的语法来实现代理模式,__OC__语言可以通过@Protocol实现协议。 代理主要由三部分组成: 协议:用来指定代理双方可以做什么,必须做什么。代理:根据指定的协议,完成委托方需要实现的功能。委托:根据指定的协议,指定代理去完成什么功能。这里用一张图来阐述一下三方之间的关系: Protocol-协议的概念从上图中我们可以看到三方之间的关系,在实际应用中通过协议来规定代理双方的行为,协议中的内容一般都是方法列表,当然也可以定义属性,我会在后续文章中顺带讲一下协议中定义属性。 协议是公共的定义,如果只是某个类使用,我们常做的就是写在某个类中。如果是多个类都是用同一个协议,建议创建一个Protocol文件,在这个文件中定义协议。遵循的协议可以被继承,例如我们常用的UITableView,由于继承自UIScrollView的缘故,所以也将UIScrollViewDelegate继承了过来,我们可以通过代理方法获取UITableView偏移量等状态参数。 协议只能定义公用的一套接口,类似于一个约束代理双方的作用。但不能提供具体的实现方法,实现方法需要代理对象去实现。协议可以继承其他协议,并且可以继承多个协议,在iOS中对象是不支持多继承的,而协议可以多继承。 // 当前协议继承了三个协议,这样其他三个协议中的方法列表都会被继承过来@protocol LoginProtocol <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;@end协议有两个修饰符@optional和@required,创建一个协议如果没有声明,默认是@required状态的。这两个修饰符只是约定代理是否强制需要遵守协议,如果@required状态的方法代理没有遵守,会报一个黄色的警告,只是起一个约束的作用,没有其他功能。 无论是@optional还是@required,在委托方调用代理方法时都需要做一个判断,判断代理是否实现当前方法,否则会导致崩溃。 示例: // 判断代理对象是否实现这个方法,没有实现会导致崩溃if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) { [self.delegate userLoginWithUsername:self.username.text password:self.password.text];}下面我们将用一个小例子来讲解一下这个问题:示例:假设我在公司正在敲代码,敲的正开心呢,突然口渴了,想喝一瓶红茶。这时我就可以拿起手机去外卖app上定一个红茶,然后外卖app就会下单给店铺并让店铺给我送过来。这个过程中,外卖app就是我的代理,我就是委托方,我买了一瓶红茶并付给外卖app钱,这就是购买协议。我只需要从外卖app上购买就可以,具体的操作都由外卖app去处理,我只需要最后接收这瓶红茶就可以。我付的钱就是参数,最后送过来的红茶就是处理结果。 但是我买红茶的同时,我还想吃一份必胜客披萨,我需要另外向必胜客app去订餐,上面的外卖app并没有这个功能。我又向必胜客购买了一份披萨,必胜客当做我的代理去为我做这份披萨,并最后送到我手里。这就是多个代理对象,我就是委托方。 在iOS中一个代理可以有多个委托方,而一个委托方也可以有多个代理。我指定了外卖app和必胜客两个代理,也可以再指定麦当劳等多个代理,委托方也可以为多个代理服务。 代理对象在很多情况下其实是可以复用的,可以创建多个代理对象为多个委托方服务,在下面将会通过一个小例子介绍一下控制器代理的复用。 下面是一个简单的代理:首先定义一个协议类,来定义公共协议 #import <Foundation/Foundation.h>@protocol LoginProtocol <NSObject>@optional- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;@end定义委托类,这里简单实现了一个用户登录功能,将用户登录后的账号密码传递出去,有代理来处理具体登录细节。 #import <UIKit/UIKit.h>#import "LoginProtocol.h"/** * 当前类是委托类。用户登录后,让代理对象去实现登录的具体细节,委托类不需要知道其中实现的具体细节。 */@interface LoginViewController : UIViewController// 通过属性来设置代理对象@property (nonatomic, weak) id<LoginProtocol> delegate;@end实现部分:@implementation LoginViewController- (void)loginButtonClick:(UIButton *)button { // 判断代理对象是否实现这个方法,没有实现会导致崩溃 if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) { // 调用代理对象的登录方法,代理对象去实现登录方法 [self.delegate userLoginWithUsername:self.username.text password:self.password.text]; }}代理方,实现具体的登录流程,委托方不需要知道实现细节。 ...

June 24, 2019 · 2 min · jiezi

设计模式-从ES5-到-TypeScript-单例模式

Back in 1994, a book was authored by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides that discusses 23 desgin patterns, titled Design Patterns: Elements of Resuable Object-Oriented Software. You may have heard of this book or the authors as Gang of Four (GoF).单例模式单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型 (Creational) 模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 注意: 单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。 关键点意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 主要解决:一个全局使用的类频繁地创建与销毁。 何时使用:当您想控制实例数目,节省系统资源的时候。 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。 关键代码:构造函数是私有的。 UML我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。 SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。 ...

June 24, 2019 · 2 min · jiezi

JavaScript-设计模式二策略模式

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换生活小栗子:诸葛锦囊 诸葛给刘备的锦囊妙计,遇到任何困难都有应对计策。策略模式实现的也是类似的场景。 再来一栗:给喜欢的女生买冰淇淋,事先不了解其喜好,只能集齐各种味道,总会命种。就是比较 “费钱”,这也是策略模式的缺点,需事先考虑所有应对场景。 模式特点策略类:算法封装成独立的函数/对象环境类:根据不同参数调用对应的策略函数/对象执行模式实现实现方式:一个基于策略模式的程序至少由两部分组成,第一个部分是一组策略类 Strategies(可变),策略类封装类具体的算法,并负责具体的计算过程。第二个部分是环境类 Context(不变), Context 接收客户的请求,随后把请求委托给某一个策略类。假设我们一个开发团队,人员组成包括(开发组长,后端,前端,测试)。开发组长领取开发任务(不变),但具体的任务执行人员可根据类型划分(可变)。 比如开发任务有以下几项: 优化服务器缓存(后端任务)优化首屏加载速度(前端任务)完成系统并发测试(测试任务)开发组长会根据任务类型,分发到对应的开发人员头上,组长不承担具体开发任务。所以每一个开发人员就承担 Strategy 的作用(独立的任务执行),而组长拥有并可支配所有开发人员的资源,充当 Context 的角色。团队每一个开发人员“组合”起来就是一个 Strategies 类(执行开发任务)。 这个 Strategies 是可变的,如果说后续开发任务需要安卓的、IOS的支持,只要添加安卓、IOS开发人员配置即可(可扩展)。 // 策略类(开发人员)var Strategies = { "backend": function(task) { console.log('进行后端任务:', task); }, "frontend": function(task) { console.log('进行前端任务:', task); }, "testend": function(task) { console.log('进行测试任务:', task); }};// 环境类(开发组长)var Context = function(type, task) { typeof Strategies[type] === 'function' && Strategies[type](task);}Context('backend', '优化服务器缓存');Context('frontend', '优化首页加载速度');Context('testend', '完成系统并发测试');上述代码带来的好处: 算法独立封装,任务分发;开发组长不承担具体开发任务(只做顶层设计,不跟年轻人抢饭碗)复用性更好,不局限于 Context 调用;开发人员不愁下家(去哪写代码都是写代码)策略模式的另一个好处就是,消除了大部分的 if...else / switch...case 条件分支语句,代码阅读性提高。 ...

June 24, 2019 · 1 min · jiezi

Java设计模式模板方法模式

定义Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.TemplateMethod lets subclasses redefine certain steps of an algorithm without changing the algorithm'sstructure.定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 模板方法模式非常简单,主要是用了Java的继承机制,话不多说,直接上代码 实现抽象模板类public abstract class AbstractClass { /** * 基本方法 */ protected abstract void doSomething(); /** * 基本方法,可以有默认实现 */ protected void doAnything() { System.out.println("AbstractClass doAnything()"); } /** * 模板方法,为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写 */ public final void templateMethod(){ doSomething(); doAnything(); }}具体模板类public class ConcreteClassA extends AbstractClass { @Override protected void doSomething() { System.out.println("ConcreteClassA doSomething()"); } @Override protected void doAnything() { System.out.println("ConcreteClassA doAnything()->我不想使用父类的默认实现,我要覆盖它"); }}public class ConcreteClassB extends AbstractClass { @Override protected void doSomething() { System.out.println("ConcreteClassB doSomething()"); } // 使用父类doAnything()的默认实现}客户端代码public class Client { public static void main(String[] args) { AbstractClass a = new ConcreteClassA(); a.templateMethod(); AbstractClass b = new ConcreteClassB(); b.templateMethod(); }}优点封装不变部分,扩展可变部分提取公共部分代码,便于维护行为由父类控制,子类实现缺点子类影响父类按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成 ...

June 24, 2019 · 2 min · jiezi

5-分钟即可掌握的-JavaScript-装饰者模式与-AOP

什么是装饰者模式当我们拍了一张照片准备发朋友圈时,许多小伙伴会选择给照片加上滤镜。同一张照片、不同的滤镜组合起来就会有不同的体验。这里实际上就应用了装饰者模式:是通过滤镜装饰了照片。在不改变对象(照片)的情况下动态的为其添加功能(滤镜)。 需要注意的是:由于 JavaScript 语言动态的特性,我们很容易就能改变某个对象(JavaScript 中函数是一等公民)。但是我们要尽量避免直接改写某个函数,这会导致代码的可维护性、可扩展性变差,甚至会污染其他业务。 什么是 AOP想必大家对"餐前洗手、饭后漱口"都不陌生。这句标语其实就是 AOP 在生活中的例子:吃饭这个动作相当于切点,我们可以在这个切点前、后插入其它如洗手等动作。 AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。利用AOP可以对业务逻辑的各个部分进行隔离,也可以隔离业务无关的功能比如日志上报、异常处理等,从而使得业务逻辑各部分之间的耦合度降低,提高业务无关的功能的复用性,也就提高了开发的效率。 在 JavaScript 中,我们可以通过装饰者模式来实现 AOP,但是两者并不是一个维度的概念。 AOP 是一种编程范式,而装饰者是一种设计模式。 ES3 下装饰者的实现了解了装饰者模式和 AOP 的概念之后,我们写一段能够兼容 ES3 的代码来实现装饰者模式: // 原函数var takePhoto =function(){ console.log('拍照片');}// 定义 aop 函数var after=function( fn, afterfn ){ return function(){ let res = fn.apply( this, arguments ); afterfn.apply( this, arguments ); return res; }}// 装饰函数var addFilter=function(){ console.log('加滤镜');}// 用装饰函数装饰原函数takePhoto=after(takePhoto,addFilter);takePhoto();这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过aop函数after来添加。 ES5 下装饰者的实现在 ES5 中引入了Object.defineProperty,我们可以更方便的给对象添加属性: let takePhoto = function () { console.log('拍照片');}// 给 takePhoto 添加属性 afterObject.defineProperty(takePhoto, 'after', { writable: true, value: function () { console.log('加滤镜'); },});// 给 takePhoto 添加属性 beforeObject.defineProperty(takePhoto, 'before', { writable: true, value: function () { console.log('打开相机'); },});// 包装方法let aop = function (fn) { return function () { fn.before() fn() fn.after() }}takePhoto = aop(takePhoto)takePhoto()基于原型链和类的装饰者实现我们知道,在 JavaScript 中,函数也好,类也好都有着自己的原型,通过原型链我们也能够很方便的动态扩展,以下是基于原型链的写法: ...

June 23, 2019 · 2 min · jiezi

设计模式之原型模式

0x01.定义与类型定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象特点:不需要知道任何创建的细节,不调用构造函数类型:创建型UML 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件: 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。Java实现/** * 原型模式 */public class Prototype implements Cloneable { private String name; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; }}/** * 测试与应用类 */public class Test { public static void main(String[] args) throws CloneNotSupportedException { Prototype prototype = new Prototype(); prototype.setName("K.O"); List<String> names = new ArrayList<>(); names.add("K.O"); prototype.setNames(names); for (int i = 0; i < 5; i ++) { Prototype p = (Prototype) prototype.clone(); p.setName("sigma"); p.getNames().add("sigma"); System.out.println(p.toString()); System.out.println(p.getName()); System.out.println(p.getNames().size()); } System.out.println(prototype.toString()); System.out.println(prototype.getName()); System.out.println(prototype.getNames().size()); }}测试输出结果org.ko.prototype.basic.Prototype@1540e19dsigma2org.ko.prototype.basic.Prototype@677327b6sigma3org.ko.prototype.basic.Prototype@14ae5a5sigma4org.ko.prototype.basic.Prototype@7f31245asigma5org.ko.prototype.basic.Prototype@6d6f6e28sigma6org.ko.prototype.basic.Prototype@135fbaa4K.O6可以看出,输出结果中对象的地址不同(是重新创建的) ...

June 22, 2019 · 5 min · jiezi

不得不知的责任链设计模式

世界上最遥远的距离,不是生与死,而是它从你的世界路过无数次,你却选择视而不见,你无情,你冷酷啊...... 被你忽略的就是责任链设计模式,希望它再次经过你身旁你会猛的发现,并对它微微一笑...... 责任链设计模式介绍抽象介绍初次见面,了解表象,深入交流之后(看完文中的 demo 和框架中的实际应用后),你我便是灵魂之交(重新站在上帝视角来理解这个概念会更加深刻) 责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象能或不能处理该请求,它都会把相同的请求传给下一个接收者,依此类推,直至责任链结束。接下来将概念图形化,用大脑图形处理区理解此概念 上图左侧的 UML 类图中,Sender 类不直接引用特定的接收器类。 相反,Sender 引用Handler 接口来处理请求handler.handleRequest(),这使得 Sender 独立于具体的接收器(概念当中说的解耦) Receiver1,Receiver2 和 Receiver3 类通过处理或转发请求来实现 Handler 接口(取决于运行时条件)上图右侧的 UML 序列图显示了运行时交互,在此示例中,Sender 对象在 receiver1 对象(类型为Handler)上调用 handleRequest(), 接收器 1 将请求转发给接收器 2,接收器 2 又将请求转发到处理(执行)请求的接收器3具象介绍大家小时候都玩过击鼓传花的游戏,游戏的每个参与者就是责任链中的一个处理对象,花球就是待处理的请求,花球就在责任链(每个参与者中)进行传递,只不过责任链的结束时间点是鼓声的结束. 来看 Demo 和实际案例 Demo设计程序猿和 log 是老交情了,使用 logback 配置日志的时候有 ConsoleAppender 和 RollingFileAppender,这两个 Appender 就组成了一个 log 记录的责任链。下面的 demo 就是模拟 log 记录:ConsoleLogger 打印所有级别的日志;EmailLogger 记录特定业务级别日志 ;FileLogger 中只记录 warning 和 Error 级别的日志 抽象概念介绍中,说过实现责任链要有一个抽象接收器接口,和具体接收器,demo 中 Logger 就是这个抽象接口,由于该接口是 @FunctionalInterface (函数式接口), 它的具体实现就是 Lambda 表达式,关键代码都已做注释标注 ...

June 21, 2019 · 4 min · jiezi

JavaScript设计模式整理

写在前面设计模式是程序员通识知识,熟练掌握并使用各种设计模式,可以体现一个程序员的工程开发水平。我花了几天时间,重温并整理了30多种设计模式,以JavaScript为示例语言。下面我会列出一些常用的设计模式说明及示例,更全面的内容见:https://github.com/yzsunlei/javascript-design-mode 什么是设计模式?一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题。另一种解释就是一个我们如何解决问题的模板 - 那些可以在许多不同的情况里使用的模板。 设计模式的分类:创建型设计模式:1、简单工厂模式 2、工厂方法模式 3、抽象工厂模式 4、建造者模式 5、原型模式 6、单例模式 结构型设计模式:7、外观模式8、适配器模式9、代理模式10、装饰者模式11、桥接模式12、组合模式13、享元模式 行为型设计模式:14、模板方法模式15、观察者模式16、状态模式17、策略模式18、职责链模式19、命令模式20、访问者模式21、中介者模式22、备忘录模式23、迭代器模式24、解释器模式 技巧型设计模式:25、链模式26、委托模式27、数据访问对象模式28、节流模式29、简单模板模式30、惰性模式31、参与者模式32、等待者模式 架构型设计模式:33、同步模块模式34、异步模块模式35、Widget模式36、MVC模式37、MVP模式38、MVVM模式 备注:该分类借鉴于《JavaScript设计模式-张容铭》工厂方法模式:通过对产品类的抽象使其创建业务主要负责用于创建多类产品的实例。 // 安全模式创建的工厂类var Factory = function(type, content) { if (this instanceof Factory) { // 保证是通过new进行创建的 var s = new this[type](content); return s; } else { return new Factory(type, content); }};// 工厂原型中设置创建所有类型数据对象的基类Factory.prototype = { Java: function(content) { }, Php: function(content) { }, JavaScript: function(content) { }};原型模式:用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法。 // 图片轮播类var LoopImages = function(imgArr, container) { this.imagesArray = imgArr; this.container = container;};LoopImages.prototype = { // 创建轮播图片 createImage: function() { console.log("LoopImages createImage function"); }, // 切换下一张图片 changeImage: function() { console.log("LoopImages changeImage function"); }};// 上下滑动切换类var SliderLoopImg = function(imgArr, container) { // 构造函数继承图片轮播类 LoopImages.call(this, imgArr, container);};SliderLoopImg.prototype = new LoopImages();// 重写继承的“切换下一张图片”方法SliderLoopImg.prototype.changeImage = function() { console.log("SliderLoopImg changeImage function");};单例模式:又称单体模式,是只允许实例化一次的对象类。 ...

June 21, 2019 · 4 min · jiezi

JavaScript设计模式一单例模式

停更许久,近期计划更新:设计模式系列。 单例模式:限制类实例化次数只能一次,一个类只有一个实例,并提供全局访问点。(归属创建型设计模式)模式特点类只有一个实例全局可访问该实例自行实例化(主动实例化)可推迟初始化,即延迟执行(与静态类/对象的区别)实现方式实现方式:使用一个变量存储类实例对象(值初始为 null/undefined )。进行类实例化时,判断类实例对象是否存在,存在则返回该实例,不存在则创建类实例后返回。多次调用类生成实例方法,返回的同一个实例对象。代码实现class Singleton { constructor(name) { this.name = name; } getName() { return this.name; }}Singleton.getInstance = (function () { let instance; return function (name) { if (!instance) { instance = new Singleton(name); } return instance; }})();var Winner = Singleton.getInstance('Winner');var Looser = Singleton.getInstance('Looser');console.log(Winner === Looser); // trueconsole.log(Winner.getName()); // 'Wiinner'console.log(Looser.getName()); // 'Looser'代码中定义了一个 Singleton 类,并定义 Singleton.getInstance() 方法来生成实例对象。而不是通过 new 操作符来创建类实例。 在 Singleton.getInstance() 中,通过闭包引用存储类实例的对象,判断类是否实例化过。(这里闭包的使用能够使 getInstance() 多次调用都能获取到之前类实例的存储值)。因此无论执行多少次 Singleton.getInstance() 方法,都只会返回同一个Singleton类实例对象。 ...

June 20, 2019 · 1 min · jiezi

动手搭建后端框架Velocity模板引擎的应用

为了提高开发效率,通常会想办法把一些模式固定的重复性的劳动抽取出来,以后再使用的时候,拿来主义就可以了。这样既可以提高开发效率,又降低了出错的风险。 这一思想在我们的日常工作中可以说随处可见,我们完成一项复杂的工程,并不需要面面俱到什么都自己写,我们完全可以利用第三方的jar包让我们达到事半功倍的效果,比如经常使用的apche的commons-lang3包。再比如java中的继承、我们自己封装的工具类等等。 另外一方面,对于源码文件,如果公司有成熟的框架,我们的开发都是遵循着框架制定的约定来进行开发的,我们在创建某一个业务的控制层、业务层、持久层的时候,实际上有相当一部分的工作是重复的。 那么对于源码文件的编写我们能否偷偷懒呢?答案肯定是可以的,我们可以利用模板引擎技术,将不变的部分写在模板文件中,将可变的部分作为变量传递到模板引擎的上下文中,最终生成我们想要的源码文件。 模板引擎的产品有很多,比如前端模板artTemplate、后端模板Velocity、FreeMarker等 本文以Velocity为例,总结一下它在实战中的应用 1.基础知识搭建过程涉及到的基础知识包括:Maven、Velocity、工厂模式、建造者模式、单元测试对于基础不熟悉的同学,建议看一下下面的两篇文章Velocity基础Velocity语法摘要 2.搭建工程2.1模块目录代码生成功能,在我设计的后台框架中,作为一个独立的模块存在,使用Maven构建。builder目录:建造者模式应用。由于代表表结构的Table实体稍显复杂,因此使用了建造者模式构建Table对象。其实不用也可以,因为Table不是很复杂,只是为了复习一下所学过的设计模式知识factory目录:工厂模式应用。在构建源码文件的时候,由于涉及到了Controller、Service、Dao、Domain这几种类型的文件,因此针对不同类型的文件,要使用其对应的处理类,因此使用了工厂模式handler目录:生成源文件的核心代码model目录:在生成domain的时候,由于字段需要从数据库中的表中读取,因此构造了与表对应的实体类方便处理utils目录:工具类Generator.java:程序主文件,调用入口test目录:单元测试 .├── generator.iml├── pom.xml└── src ├── main │   ├── java │   │   └── com │   │   └── wt │   │   └── master │   │   └── generator │   │   ├── Generator.java │   │   ├── builder │   │   │   ├── MySqlTableBuilder.java │   │   │   └── TableBuilder.java │   │   ├── factory │   │   │   └── GeneratorFactory.java │   │   ├── handler │   │   │   ├── BaseGenerator.java │   │   │   ├── ControllerGeneratorHandler.java │   │   │   ├── DomainGeneratorHandler.java │   │   │   ├── MapperGeneratorHandler.java │   │   │   └── ServiceGeneratorHandler.java │   │   ├── model │   │   │   └── Table.java │   │   └── util │   │   ├── JdbcUtils.java │   │   ├── SpringContextUtils.java │   │   ├── TableColumnUtils.java │   │   └── TableInfoUtils.java │   └── resources │   ├── config │   │   ├── applicationContext.xml │   │   └── db.properties │   └── template │   ├── controller.java.vm │   ├── dao.java.vm │   ├── domain.java.vm │   ├── service.java.vm │   └── serviceimpl.java.vm └── test └── com.wt.master.generator └── GeneratorTest.java2.2引入依赖<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>j2ee</artifactId> <groupId>com.wt.master</groupId> <version>1.0-SNAPSHOT</version> <relativePath>../version/</relativePath> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>generator</artifactId> <dependencies> <!-- 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>com.wt.master</groupId> <artifactId>core</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency> </dependencies></project>3.核心代码3.1模板文件的定义以controller层生成模板为例将不变的部分直接写到.vm文件中将模板文件中,有可能发生变化的部分,抽取为变量,变量的值从VelocityContext中获取在Velocity架构中,有一个上下文的定义,通过上下文,程序将变量放入上下文对象中。而模板从上下文中获取对应变量的值,获取的方式是${变量名},关于Velocity模板文件中的语法,参见上文提到的两篇文章 ...

June 19, 2019 · 5 min · jiezi

javascript设计模式学习笔记之命令模式

命令模式指的是一个执行某些特定事情的指令设计模式的主题: 总是把不变的事物和变化的事物分离开来在javascript 中, 函数作为一等对象, 所以, 命令模式其实是回调函数的一个面向对象的替代品 // 设置命令 var setCommand = function(button, func) { button.onclick = function () { func(); } } // 命令集合 var MenuBar = { refresh: function() { console.log('刷新菜单界面'); } }; // 命令触发条件 receiver 接受者 var RefreshMenuBarCommand = function (receiver) { return function() { receiver.refresh(); } } var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar); // 绑定命令 setCommand(button1, refreshMenuBarCommand);

June 16, 2019 · 1 min · jiezi

Java设计模式工厂模式

一、简单工厂定义简单工厂其实并不属于23种GOF设计模式之一,该模式是工厂方法模式的弱化(或者说是工厂方法模式的一种特例),因为简单,所以称为简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式。虽然不是"标准"的设计模式(更像是一种编程习惯),但在实际项目中,采用该方法的案例还是比较多的。 简单工厂模式没有严格的定义,我们姑且使用以下描述: 提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类关键点具体的工厂类创建产品的工厂方法 静态方法(静态工厂名字的由来)通常会有一个"类型"参数(还有一种是可以提供多个静态工厂方法并通过不同方法名区别要创建的产品)根据参数,利用if或者switch创建产品并返回实现抽象产品类(接口或抽象类)public interface Product { void doSomething(); void doAnything();}具体产品类public class ConcreteProductA implements Product { @Override public void doSomething() { System.out.println("ConcreteProductA doSomething"); } @Override public void doAnything() { System.out.println("ConcreteProductA doAnything"); }}public class ConcreteProductB implements Product { @Override public void doSomething() { System.out.println("ConcreteProductB doSomething"); } @Override public void doAnything() { System.out.println("ConcreteProductB doAnything"); }}工厂类public class Creator { public static Product createProduct(String type) { Product product = null; switch (type) { case "A": product = new ConcreteProductA(); break; case "B": product = new ConcreteProductB(); break; } return product; }}客户端代码public class Client { public static void main(String[] args) { Product productA = Creator.createProduct("A"); productA.doSomething(); productA.doAnything(); Product productB = Creator.createProduct("B"); productB.doSomething(); productB.doAnything(); }}优点简单解耦 ...

June 15, 2019 · 3 min · jiezi

策略模式

定义:定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。类型:行为型适用场景: 系统有很多类,而它们的区别仅仅在于它们的行为不同一个系统需要动态地在几种算法中选择一种优点: 开闭原则避免使用多重条件转移语句(if...else...,switch)提高算法的保密性和安全性缺点: 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。产生很多策略类案例某知识平台的教学视频促销(满减、立减、返现) 促销策略接口 public interface PromotionStrategy { void doPromotion();}返现策略 public class FanXianPromotionStrategy implements PromotionStrategy{ @Override public void doPromotion() { System.out.println("返现促销,返回的金额存放到慕课网用户的余额中"); }}立减策略 public class LiJianPromotionStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("立减促销,课程的价格直接减去配置的价格"); }}满减策略 public class ManJianPromotionStrategy implements PromotionStrategy{ @Override public void doPromotion() { System.out.println("满减促销,满200-20元"); }}促销活动 public class PromotionActivity { private PromotionStrategy promotionStrategy; public PromotionActivity(PromotionStrategy promotionStrategy) { this.promotionStrategy = promotionStrategy; } public void executePromotionStrategy(){ promotionStrategy.doPromotion(); }}public class Test { public static void main(String[] args) { PromotionActivity promotionActivity618 = new PromotionActivity(new LiJianPromotionStrategy()); PromotionActivity promotionActivity1111 = new PromotionActivity(new FanXianPromotionStrategy()); promotionActivity618.executePromotionStrategy(); promotionActivity1111.executePromotionStrategy(); }}结果: ...

June 13, 2019 · 1 min · jiezi

Java设计模式单例模式Singleton-Pattern

定义单例模式是一个比较"简单"的模式,其定义如下: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。或者 Ensure a class has only one instance, and provide a global point of access to it.确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 请注意"简单"二字的双引号,说它简单它也简单,但是要想用好、用对其实并不那么简单,为什么这么说? 首先,单例模式的定义比较好理解,应用场景明确,实现思路比较简单;其次,单例模式其实要考虑的因素很多,诸如延迟加载、线程安全以及破坏单例的情况等等。也正是这些因素导致单例模式的实现方式多样,且各有利弊特点单例类只能有一个实例;单例类必须自己创建自己的唯一实例;单例类必须给所有其他对象提供这一实例。基本步骤私有的静态成员变量:在本类中创建唯一实例,使用静态成员变量保存;为保证安全性,私有化这个成员变量私有的构造方法:避免其他类可以直接创建单例类的对象公有的静态方法:供其他类获取本类的唯一实例考虑的因素延迟加载线程安全破坏单例的情况 序列化 如果Singleton类是可序列化的,仅仅在生声明中加上implements Serializable是不够的。为了维护并保证Singleton,必须声明所有实例域都是瞬时(transient)的,并且提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的对象。反射 授权的客户端可以通过反射来调用私有构造方法,借助于AccessibleObject.setAccessible方法即可做到 。如果需要防范这种攻击,请修改构造函数,使其在被要求创建第二个实例时抛出异常。private Singleton() { System.err.println("Singleton Constructor is invoked!"); if (singleton != null) { System.err.println("实例已存在,无法初始化!"); throw new UnsupportedOperationException("实例已存在,无法初始化!"); } }}对象复制 在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过对象复制方式创建一个新对象,对象复制是不用调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。在一般情况下,类复制的情况不需要考虑,很少会出现一个单例类会主动要求被复制的情况,解决该问题的最好方法就是单例类不要实现Cloneable接口。类加载器如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。 实现方式1、懒汉式线程不安全(适用于单线程)public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }}优点:延迟加载缺点:线程不安全,多线程环境下有可能产生多个实例为解决懒汉式"线程安全问题",可以将getInstance()设置为同步方法,于是就有了第二种实现方式: ...

June 13, 2019 · 2 min · jiezi

抽象工厂

抽象工厂定义: 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口无需指它们具体的类类型:创建型适用场景: 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节强调一系列相关的产品对象(属于同一产品)一起使用创建对象需要大量重复的代码提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。优点: 具体产品在应用层代码隔离,无需关心创建细节。将一个系列的产品族统一到一起创建。缺点: 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。增加了系统的抽象性和理解难度。产品等级结构和产品族 案例视频教程=视频+笔记 public abstract class Article { public abstract void produce();}public abstract class Video { public abstract void produce();}public class JavaArticle extends Article { @Override public void produce() { System.out.println("编写Java课程手记"); }}public class JavaVideo extends Video { @Override public void produce() { System.out.println("录制Java课程视频"); }}public class PythonArticle extends Article { @Override public void produce() { System.out.println("编写Python课程手记"); }}public class PythonVideo extends Video { @Override public void produce() { System.out.println("录制Python课程视频"); }}public interface CourseFactory { Video getVideo(); Article getArticle();}public class JavaCourseFactory implements CourseFactory { @Override public Video getVideo() { return new JavaVideo(); } @Override public Article getArticle() { return new JavaArticle(); }}public class PythonCourseFactory implements CourseFactory { @Override public Video getVideo() { return new PythonVideo(); } @Override public Article getArticle() { return new PythonArticle(); }}public class Test { public static void main(String[] args) { CourseFactory courseFactory = new JavaCourseFactory(); Video video = courseFactory.getVideo(); Article article = courseFactory.getArticle(); video.produce(); article.produce(); }} ...

June 12, 2019 · 1 min · jiezi

工厂方法

工厂方法定义:定义一个创建对象的接口,但让实现这个接口的类决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。类型:创建型适用场景: 创建对象需要大量重复的代码客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。一个类通过其子类来指定创建哪个对象。优点: 用户只需要关心所需产品对应的工厂,无需关心创建的细节。加入新产品符合开闭原则,提高可扩展性。缺点: 类的个数容易过多,增加复杂度。增加了系统的抽象性和理解难度。案例public abstract class Video { public abstract void produce();}public class JavaVideo extends Video { @Override public void produce() { System.out.println("录制Java课程视频"); }}public class PythonVideo extends Video { @Override public void produce() { System.out.println("录制Python课程视频"); }}public abstract class VideoFactory { public abstract Video getVideo();}public class JavaVideoFactory extends VideoFactory { @Override public Video getVideo() { return new JavaVideo(); }}public class PythonVideoFactory extends VideoFactory { @Override public Video getVideo() { return new PythonVideo(); }}public class Test { public static void main(String[] args) { VideoFactory videoFactory = new PythonVideoFactory(); Video video = videoFactory.getVideo(); video.produce(); }}可扩展性:增加一个前端视频: ...

June 12, 2019 · 1 min · jiezi

简单工厂

简单工厂定义:由一个工厂对象决定创建出哪一种产品类的实例类型:创建性,但不属于GOF23种设计模式适用场景 工厂类负责创建的对象比较少客户端(应用层)只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心优点:只需要传入一个正确的参数,就可以获取你所需要的对象而无需知道其创建细节。缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则。案例public abstract class Video { public abstract void produce();}public class JavaVideo extends Video { @Override public void produce() { System.out.println("录制Java课程视频"); }}public class PythonVideo extends Video { @Override public void produce() { System.out.println("录制Python课程视频"); }}public class Test { public static void main(String[] args) { Video video = new PythonVideo(); video.produce(); }}思考Video video = new PythonVideo();这行在客户端的代码使得客户端不但知道了Video抽象类还知道了其实现类PythonVideo。接口的思想是“封装隔离”,而实现类PythonVideo应该是被抽象类Video封装并同客户端隔离开的,即客户端根本就应该不知道具体的实现类是PythonVideo。 于是乎我们就拿走new PythonVideo( );但是我们却无法得到Video对象。就像现在的Client,它知道要使用Video接口,但是不知道由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,那该怎么办?于是乎简单工厂就来了。 public class VideoFactory { //【1】反射 public Video getVideo(Class c){ Video video = null; try { video = (Video) Class.forName(c.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return video; } public Video getVideo(String type){ if("java".equalsIgnoreCase(type)){ return new JavaVideo(); }else if("python".equalsIgnoreCase(type)){ return new PythonVideo(); } return null; }}public class Test { public static void main(String[] args) { VideoFactory videoFactory = new VideoFactory(); Video video = videoFactory.getVideo("java"); if(video == null){ return; } video.produce();/* VideoFactory videoFactory = new VideoFactory(); Video video = videoFactory.getVideo(JavaVideo.class); if(video == null){ return; } video.produce();*/ }}问题:如果需要增加其他类型的视频,就需要在VideoFactory中修改添加相应的代码,这个时候可以使用工厂方法。 ...

June 12, 2019 · 1 min · jiezi

通熟易懂的设计模式二

组合模式(Composite pattern)组合模式看起来就像对象组的树形结构,一个对象里面包含一个或一组其他的对象。它是属于结构型模式。例如,一个公司包括很多个部门,每个部门又包括很多人,这个用数据结构来表示就是树形结构,实际上也是用到来组合模式,多个人组成一个部门,多个部门组成一个公司。 例如,我们用下面这个公司、部门、员工的例子来更好的理解组合模式。 class Company { private String name; private List<Dept> depts;}class Dept { private String name; private List<User> users;}class User { private String name;}装饰模式(Decorator pattern)装饰器设计模式允许我们动态地向对象添加功能和行为,而不会影响同一类中其他现有对象的行为。并且可以根据我们的要求和选择将此自定义功能应用于单个对象。假如使用继承来扩展类的行为,这发生在编译期,该类的所有实例都获得扩展行为。 装饰器设计模式的特点:它允许我们在运行时向对象(而不是类)添加功能。它是一种结构模式,它为现有类提供了一个包装器。它使用抽象类或接口与组合来实现包装器。它创建装饰器类,它包装原始类并通过保持类方法的签名不变来提供其他功能。它最常用于应用单一责任原则,因为我们将功能划分为具有独特关注区域的类。 例如,我们用下面这个画图形的例子来更好的理解装饰模式。 //定义一个形状的接口public interface Shape { void draw(); void resize();}//一个画圆的实现public class Circle implements Shape { @Override public void draw() { System.out.println("Drawing Circle"); } @Override public void resize() { System.out.println("Resizing Circle"); }}//一个画矩形的实现public class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing Rectangle"); } @Override public void resize() { System.out.println("Resizing Rectangle"); }}//定义一个形状的装饰器抽象类,并用组合模式定义一个形状的属性public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { super(); this.decoratedShape = decoratedShape; }}//颜色的枚举public enum Color { RED, GREEN, BLUE}//线条样式的枚举public enum LineStyle { SOLID, DASH, DOT}//定义一个填充颜色的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景public class FillColorDecorator extends ShapeDecorator { protected Color color; public FillColorDecorator(Shape decoratedShape, Color color) { super(decoratedShape); this.color = color; } @Override public void draw() { decoratedShape.draw(); System.out.println("Fill Color: " + color); } @Override public void resize() { decoratedShape.resize(); }}//定义一个线条样式的实现类实现装饰器,并重写 draw() 方法,resize() 方法我们可以保持不变,也可以自定义,看使用场景public class LineStyleDecorator extends ShapeDecorator { protected LineStyle style; public LineStyleDecorator(Shape decoratedShape, LineStyle style) { super(decoratedShape); this.style = style; } @Override public void draw() { decoratedShape.draw(); System.out.println("Line Style: " + style); } // @Override public void resize() { decoratedShape.resize(); }}//使用装饰器模式public class Client { public static void main(String[] args) { //在使用时可以任意组装,提升代码灵活性和扩展性。 Shape circle = new FillColorDecorator(new LineStyleDecorator(new Circle(), LineStyle.DASH), Color.RED); circle.draw(); }}外观模式(Facade Pattern)它提供了一个可以访问系统的接口,这个接口里面的实现可能很复杂,调用了其他多个接口,我们并不知道它里面的具体实现,隐藏了系统的复杂性。它属于结构型模式。 ...

June 12, 2019 · 5 min · jiezi

设计模式状态模式

状态模式想必每个程序员坐电梯的时候肯定对电梯的工作流程都有过一定的思考,今天我们就来谈谈电梯是如何工作的,是什么样的机制保证了它在停下来的时候才会开门,在关了门之后才会启动,这种状态与行为之间的关系到底是如何控制的呢,这时状态模式就派上用场啦 定义允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式 动机在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。 结构 组件Context(环境类)环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。 State(抽象状态类)它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中 ConcreteState(具体状态类)它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。 实现这里我们模拟一个QQ的状态转换过程 抽象状态类 package state;public interface State { public void handle(Context context);}拥有多种状态的对象 package state;public class Context { private State state; public State getState() { return state; } public void setState(State state) { this.state = state; } public Context(State state) { this.state = state; } public void contexthandle(){ state.handle(this); }}具体状态类 package state;public class ConcreteState implements State { @Override public void handle(Context context) { System.out.println("忙碌状态"); //状态转换 context.setState(new ConcreteStateC()); }}具体状态类 ...

June 11, 2019 · 1 min · jiezi

通熟易懂的设计模式一

写在前面评判一个程序员是否优秀,就是 show me the code。优秀的代码可读性强,高内聚低耦合,可扩展。想要写优秀的代码,做个优秀的程序员,就需要多看看大牛写的开源框架,吸取其中的精华,多学学设计模式,除此之外,没有任何其他捷径。 设计模式主要分为创建型模式、结构型模式、行为型模式三种类型。 工厂方法(Factory method pattern)定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行,它属于创建型模式。 工厂对象通常包含一个或多个方法,用来创建这个工厂所能创建的各种类型的对象。这些方法可能接收参数,用来指定对象创建的方式,最后返回创建的对象。 工厂通常是一个用来创建其他对象的对象。工厂是构造方法的抽象,用来实现不同的分配方案。 维基百科工厂方法的例子 // 定义了 Button 如何创建public interface Button{}// 实现了 WinButton public class WinButton implements Button{}// 实现了 MacButton public class MacButton implements Button{}// 创建 Button 的工厂类public interface ButtonFactory { Button createButton();}// 真正创建 WinButton 的实现类,实现了 ButtonFactorypublic class WinButtonFactory implements ButtonFactory { @Override public static Button createButton(){ return new WinButton(); }}// 真正创建 MacButton的实现类,实现了 ButtonFactorypublic class MacButtonFactory implements ButtonFactory { @Override public static Button createButton(){ return new MacButton(); }}抽象工厂模式(Abstract factory pattern)将一组具有同一主题的单独的工厂封装起来。在使用中,使用方需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一方法的具体对象。它属于创建型模式。 ...

June 11, 2019 · 3 min · jiezi

PHP设计模式八桥接模式Bridge-For-PHP

桥接设计模式桥接模式:将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果。 案例模拟毛笔需求:现在需要准备三种粗细(大中小),并且有五种颜色的比如果使用蜡笔,我们需要准备3*5=15支蜡笔,也就是说必须准备15个具体的蜡笔类。而如果使用毛笔的话,只需要3种型号的毛笔,外加5个颜料盒,用3+5=8个类就可以实现15支蜡笔的功能。实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。即将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。关键就在于能否脱耦。蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建15种对象才能完成任务。而毛笔与颜料能够很好的脱耦(比和颜色是分开的),抽象层面的概念是:"毛笔用颜料作画",每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。模拟企业分组发送短信需求:公司现在需要按分组(临时工、正式工、管理层等)以多种形式(QQ、Email、微博等)给员工发送通知。适用性类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。通过使用Bridge模式对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。不希望在抽象和它的实现部分之间有一个固定的绑定关系。一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。效果Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同员工组和不同信息发送模式。Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。实现员工分组abstract class Staff{ abstract public function staffData();}class CommonStaff extends Staff{ public function staffData() { return "小名,小红,小黑"; }}class VipStaff extends Staff{ public function staffData() { return '小星、小龙'; }}发送形式// 抽象父类abstract class SendType{ abstract public function send($to, $content);}class QQSend extends SendType{ public function __construct() { // 与QQ接口连接方式 } public function send($to, $content) { return $content. '(To '. $to . ' From QQ)<br>'; }}class SendInfo{ protected $_level; protected $_method; public function __construct($level, $method) { // 这里可以使用单例控制资源的消耗 $this->_level = $level; $this->_method = $method; } public function sending($content) { $staffArr = $this->_level->staffData(); $result = $this->_method->send($staffArr, $content); echo $result; }}客户端调用$info = new SendInfo(new VipStaff(), new QQSend());$info->sending( '回家吃饭');$info = new SendInfo(new CommonStaff(), new QQSend());$info->sending( '继续上班');输出结果回家吃饭(To 小星、小龙 From QQ)继续上班(To 小名,小红,小黑 From QQ)总结从上面可以看出,如果增加分组或者是发送信息的类型,都可以直接创建一个类,来拓展,十分方便。但是Bridge模式虽然是一个非常有用的模式,也非常复杂,它很好的符合了开放-封闭原则和优先使用对象,而不是继承这两个面向对象原则。

June 10, 2019 · 1 min · jiezi

从观察者模式到迭代器模式系统讲解-RxJS-Observable一

RxJS 是 Reactive Extensions for JavaScript 的缩写,起源于 Reactive Extensions,是一个基于可观测数据流 Stream 结合观察者模式和迭代器模式的一种异步编程的应用库。RxJS 是 Reactive Extensions 在 JavaScript 上的实现。 Reactive Extensions(Rx)是对 LINQ 的一种扩展,他的目标是对异步的集合进行操作,也就是说,集合中的元素是异步填充的,比如说从 Web或者云端获取数据然后对集合进行填充。LINQ(Language Integrated Query)语言集成查询是一组用于 C# 和Visual Basic 语言的扩展。它允许编写 C# 或者 Visual Basic 代码以操作内存数据的方式,查询数据库。RxJS 的主要功能是利用响应式编程的模式来实现 JavaScript 的异步式编程(现前端主流框架 Vue React Angular 都是响应式的开发框架)。 RxJS 是基于观察者模式和迭代器模式以函数式编程思维来实现的。学习 RxJS 之前我们需要先了解观察者模式和迭代器模式,还要对 Stream 流的概念有所认识。下面我们将对其逐一进行介绍,准备好了吗?让我们现在就开始吧。 RxJS 前置知识点观察者模式观察者模式又叫发布订阅模式(Publish/Subscribe),它是一种一对多的关系,让多个观察者(Obesver)同时监听一个主题(Subject),这个主题也就是被观察者(Observable),被观察者的状态发生变化时就会通知所有的观察者,使得它们能够接收到更新的内容。 观察者模式主题和观察者是分离的,不是主动触发而是被动监听。 举个常见的例子,例如微信公众号关注者和微信公众号之间的信息订阅。当微信用户关注微信公众号 webinfoq就是一个订阅过程,webinfoq负责发布内容和信息,webinfoq有内容推送时,webinfoq的关注者就能收到最新发布的内容。这里,关注公众号的朋友就是观察者的角色,公众号webinfoq就是被观察者的角色。 示例代码: // 定义一个主题类(被观察者/发布者)class Subject { constructor() { this.observers = []; // 记录订阅者(观察者)的集合 this.state = 0; // 发布的初始状态 } getState() { return this.state; } setState(state) { this.state = state; // 推送新信息 this.notify(); // 通知订阅者有更新了 } attach(observer) { this.observers.push(observer); // 对观察者进行登记 } notify() { // 遍历观察者集合,一一进行通知 this.observers.forEach(observer = { observer.update(); }) }}// 定义一个观察者(订阅)类class Observer { constructor(name, subject) { this.name = name; // name 表示观察者的标识 this.subject = subject; // 观察者订阅主题 this.subject.attach(this); // 向登记处传入观察者实体 } update() { console.log(`${this.name} update, state: ${this.subject.getState()}`); }}// 创建一个主题let subject = new Subject();// 创建三个观察者: observer$1 observer$2 observer$3let observer$1 = new Observer("observer$1", subject);let observer$2 = new Observer("observer$2", subject);let observer$3 = new Observer("observer$3", subject);// 主题有更新subject.setState(1);subject.setState(2);subject.setState(3);// 输出结果// observer$1 update, state: 1// observer$1 update, state: 1// observer$1 update, state: 1// observer$2 update, state: 2// observer$2 update, state: 2// observer$2 update, state: 2// observer$3 update, state: 3// observer$3 update, state: 3// observer$3 update, state: 3迭代器模式迭代器(Iterator)模式又叫游标(Sursor)模式,迭代器具有 next 方法,可以顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表现。 ...

June 10, 2019 · 5 min · jiezi

设计模式6原则

1.开闭原则,简言之:对扩展开放,对修改关闭,2.单一职责原则,一个类只负责一项指责,比如:User是一个职责,Order是一个职责3.里氏替换原则(LSP): 3.1子类可以按需扩展自由功能3.2不修改父类原有功能。如果是继承而非实现的时候,少用@override4.依赖倒转原则: 引用文字设计模式六大原则(3):依赖倒置原则定义问题由来解决方案定       义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 解决方案:将类A修改为依赖接口O,类B和类C各自实现接口O,类A通过接口O间接与类B或者类C发生联系,则会大大降低修改类A的几率。 依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在Java中,抽象指的是接口或者抽象类,细节就是实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。 依赖倒置原则的核心思想是面向接口编程,我们依然用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。场景是这样的,母亲给孩子讲故事,只要给她(母亲)一本书,她就可以照着书给孩子讲故事了。代码如下: class Book{ public String getContent(){ return "很久很久以前有一个阿拉伯的故事......";}} class Mother{ public void narrate(Book book){ System.out.println("妈妈开始讲故事了。");}} public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.narrate(new Book());}}运行结果: 妈妈开始讲故事很久很久以前有一个阿拉伯的故事......运行良好,假设有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下: class Newspaper{ public String getContent(){ return "林书豪38+7领导尼克斯击败湖人......";}}我们发现这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成了报纸,居然要我们必须修改Mother类才能读。假如以后需求换成杂志呢,换成网页呢?难道每换一次都要我们修改一次Mother类吗?这显然不是最好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们的耦合度才行。 我们引入一个抽象的接口IReader。读物,包括我们的博客文章,只要是带字的都是读物。代码如下: interface IReader{ public String getContent();}Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为: /***让Newspaper去实现IReader这个接口*/class Newspaper implements IReader{ public String getContent(){ return "林书豪17+9助尼克斯击败湖人......";}} /***让Book类也去实现IReader这个借口*/class Book implements IReader{ ...

June 10, 2019 · 1 min · jiezi

设计模式6大原则

1.开闭原则,简言之:对扩展开放,对修改关闭,2.单一职责原则,一个类只负责一项指责,比如:User是一个职责,Order是一个职责3.里氏替换原则(LSP): 3.1子类可以按需扩展自由功能3.2不修改父类原有功能。如果是继承而非实现的时候,少用@override4.依赖倒转原则: 引用文字设计模式六大原则(3):依赖倒置原则定义问题由来解决方案定       义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 解决方案:将类A修改为依赖接口O,类B和类C各自实现接口O,类A通过接口O间接与类B或者类C发生联系,则会大大降低修改类A的几率。 依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在Java中,抽象指的是接口或者抽象类,细节就是实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。 依赖倒置原则的核心思想是面向接口编程,我们依然用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。场景是这样的,母亲给孩子讲故事,只要给她(母亲)一本书,她就可以照着书给孩子讲故事了。代码如下: class Book{ public String getContent(){ return "很久很久以前有一个阿拉伯的故事......";}} class Mother{ public void narrate(Book book){ System.out.println("妈妈开始讲故事了。");}} public class Client{ public static void main(String[] args){ Mother mother = new Mother(); mother.narrate(new Book());}}运行结果: 妈妈开始讲故事很久很久以前有一个阿拉伯的故事......运行良好,假设有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下: class Newspaper{ public String getContent(){ return "林书豪38+7领导尼克斯击败湖人......";}}我们发现这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成了报纸,居然要我们必须修改Mother类才能读。假如以后需求换成杂志呢,换成网页呢?难道每换一次都要我们修改一次Mother类吗?这显然不是最好的设计。原因就是Mother与Book之间的耦合性太高了,必须降低他们的耦合度才行。 我们引入一个抽象的接口IReader。读物,包括我们的博客文章,只要是带字的都是读物。代码如下: interface IReader{ public String getContent();}Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为: /***让Newspaper去实现IReader这个接口*/class Newspaper implements IReader{ public String getContent(){ return "林书豪17+9助尼克斯击败湖人......";}} /***让Book类也去实现IReader这个借口*/class Book implements IReader{ ...

June 10, 2019 · 1 min · jiezi

Java设计模式六大原则

1、单一职能原则(Single Responsibility Principle, SRP)定义There should never be more than one reason for a class to change.应该有且仅有一个原因引起类的变更 换言之,也就是一个接口或类只有一个职责 好处类的复杂性降低,实现什么职责都有清晰明确的定义;可读性提高,复杂性降低,那当然可读性提高了;可维护性提高,可读性提高,那当然更容易维护了;变更引起的风险降低,变更时必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。最佳实践职责的划分很难,要想完全符合单一职责的设计更难,原则是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化 2、里氏替换原则(LiskovSubstitution Principle,LSP)继承的利与弊优点 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;提高代码的重用性;子类可以形似父类,但又异于父类;提高代码的可扩展性,实现父类的方法就可以“为所欲为”了;提高产品或项目的开放性。缺点 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。定义If for each object o1 of type S there is an object o2 oftype T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 issubstituted for o2 then S is a subtype of T.如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。 以上是LSP"最正宗"的定义,但是可能不太容易理解,它还有另外一种相对清晰明确的定义: Functions that use pointers or references to base classes must be able to useobjects of derived classes without knowing it.所有引用基类的地方必须能透明地使用其子类的对象。 ...

June 10, 2019 · 2 min · jiezi

javascript设计模式学习笔记之发布订阅模式

发布-订阅模式定义对象间的一种 一对多 的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都将得到通知简单实现 // 定义发布者 var salesOffices = {}; // 缓存列表, 存放订阅者的回调函数 salesOffices.clientList = []; // 定义订阅者 salesOffices.listen = function (fn) { this.clientList.push(fn); } // 发布消息 salesOffices.trigger = function () { for (var i = 0, fn; fn = this.clientList[i++];) { fn.apply(this, arguments) } } /*** 测试 ***/ // 订阅者1 salesOffices.listen(function (price, squareMeter) { console.log('价格=' + price); console.log('squareMeter= ' + squareMeter); }); // 订阅者2 salesOffices.listen(function (price, squareMeter) { console.log('价格=' + price); console.log('squareMeter= ' + squareMeter); }); // 发布消息 salesOffices.trigger(2000, 80); salesOffices.trigger(3000, 100);上面的实现方式, 导致了, 每个订阅者都会收到发布者发布的消息 // 定义发布者 var salesOffices = {}; // 缓存列表, 存放订阅者的回调函数 salesOffices.clientList = {}; // 定义订阅者 (增加缓存, 增加标示 key ) salesOffices.listen = function (key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); } // 发布消息 salesOffices.trigger = function () { var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false; } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments) } }提炼发布-订阅模式 // 核心事件 var event = { clientList: {}, listen: function (key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); }, trigger: function () { var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } } } // 取消订阅消息 event.remove = function (key, fn) { var fns = this.clientList[key]; if (!fns) { return false; } if (!fn) { // 没有传入fn 取消key对应的所有的订阅 fns && (fns.length = 0); } else { // 传入fn 删除对应的fn for (var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l]; if (_fn === fn) { fns.splice(l, 1) } } } } // 给任何对象动态增加发布-订阅功能 var installEvent = function (obj) { for (var key in event) { if (event.hasOwnProperty(key)) { obj[key] = event[key]; } } } /*** 测试 ***/ var salesOffices = {}; installEvent(salesOffices); // 订阅者1 salesOffices.listen('squareMeter80', function (price) { console.log('价格=' + price); }); // 订阅者2 salesOffices.listen('squareMeter100', function (price) { console.log('价格=' + price); }); // 发布消息 salesOffices.trigger('squareMeter80', 20000); salesOffices.trigger('squareMeter100', 30000);登录案例 // 登录成功, 发布成功消息 $.ajax('http://xxx.com/login', function (data) { login.trigger('loginSuccess', data); }); // 这种写法也很好 var header = (function () { // 订阅消息 login.listen('loginSuccess', function (data) { header.setAvatar(data); }) return { setAvatar: function (data) { console.log('header 模块拿到用户信息') } } })();以上写法会有三个问题 ...

June 8, 2019 · 5 min · jiezi

设计模式之单例模式

0x01.定义与类型定义:保证一个类仅有一个实例,并提供一个全局访问点类型:创建型UML 单例模式的基本要素 私有的构造方法指向自己实例的私有静态引用以自己实例为返回值的静态的公有的方法0x02.适用场景像确保任何情况下都绝对只有一个实例需要频繁实例化然后销毁的对象。创建对象时耗时过多或者耗资源过多,但又经常用到的对象。有状态的工具类对象。频繁访问数据库或文件的对象。0x03.单例模式的优缺点1.优点在内存里只有一个实例,减少了内存开销可以避免对资源的多重占用避免重复创建对象,提高性能设置全局访问点,严格控制访问2.缺点没有接口,扩展困难违反开闭原则0x04.单例模式的几种实现方式1.饿汉式饿汉式:顾名思义,对象比较饥饿,所以一开始就创建好了。饿汉式也是单例模式的最简单实现。Java实现/** * 饿汉式 * 一开始就new好了 */public class HungrySingleton implements Serializable { /** * 可以直接new也可以适用静态块中创建 * */ private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton1(); } public static HungrySingleton getInstance() { return hungrySingleton; } /** * 私有构造函数 */ private HungrySingleton() {}}饿汉式的单例模式,对象一开始就创建好了。不需要考虑线程安全问题。饿汉式单例模式如果消耗资源比较多,而对象未被适用则会造成资源浪费。2.懒汉式懒汉式:说明类对象比较懒,没有直接创建,而是延迟加载的,是第一次获取对象的时候才创建。懒汉式的单例模式应用较多。a.第一个版本的Java实现(非线程安全)/** * 懒汉式 * 线程不安全 */public class LazySingleton { private static LazySingleton lazySingleton = null; //线程不安全,当有两个线程同时创建对象,会违背单例模式 public static LazySingleton getInstance() { if (lazySingleton == null) { //会发生指令重排 lazySingleton = new LazySingleton(); } return lazySingleton; } private LazySingleton() {}}这个版本的懒汉式会出现线程安全的问题,当两个线程同时访问getInstance()静态方法时,lazySingleton还未创建,就会创建出两个实例,违背了单例模式。这里可以在getInstance()方法添加同步锁synchronized解决,也可以在方法体添加类锁,但是这样相当于完全锁住了getInstance(),会出现性能问题。推荐适用下面这种方式b.双重检查锁double check懒汉式(线程安全,通常适用这种方式)/** * 懒汉式 * 线程不安全 */public class LazyDoubleCheckSingleton { //volatile 禁止指令重排序 private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; /** * 在静态方法中直接加synchronized相当于锁了类 * @return */ public static LazyDoubleCheckSingleton getInstance() { //同样实锁类, 指令重排序 if (lazyDoubleCheckSingleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton == null) { /** * 1.分配内存给这个对象 * 2.初始化对象 * 3.设置lazyDoubleCheckSingleton指向刚分配的内存 * 2 3 顺序有可能发生颠倒 * intra-thread semantics 不会改变单线程执行结果,指令重排序 */ lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } private LazyDoubleCheckSingleton() {}}双重检查,只有对象为空的时候才会需要同步锁,而第二次判断是否为null,是对象是否已经创建。添加volatile关键字,防止指令重排序。c.基于静态内部类的延迟加载方案私有静态类的延迟加载public class StaticInnerClassSingleton { /** * 看静态类的初始化锁那个线程可以拿到 */ private static class InnerClass { private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance() { return InnerClass.staticInnerClassSingleton; } private StaticInnerClassSingleton () { if (InnerClass.staticInnerClassSingleton != null) { throw new RuntimeException("单例对象禁止反射调用"); } }}将延迟初始化交给静态类的初始化3.容器单例使用静态容器方式来实现多单例类public class ContainerSingleton { //静态容器, 注意map不是线程安全的,如果为了线程安全可以使用HashTable或者ConcurrentHashMap private static Map<String, Object> singletonMap = new HashMap<>(); public static void putInstance (String key, Object instance) { if (key != null && key.length() != 0) { if (!singletonMap.containsKey(key)) { singletonMap.put(key, instance); } } } public static Object getInstance (String key) { return singletonMap.get(key); }}容器单例如果要保证线程安全性,建议使用ConcurrentHashMap通常使用容器单例情况是:单例对象比较多,需要统一维护。4.枚举单例模式(推荐使用)枚举单例是从JVM层面上做的限制public enum EnumInstance { /** * 具体的单例实例 */ INSTANCE { protected void printTest () { System.out.println("K.O print Test!"); } }; private Object data; protected abstract void printTest(); public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumInstance getInstance() { return INSTANCE; }}后续会介绍到,单例模式完美防御了反射与序列化攻击5.ThreadLocal线程单例(并不是严格意义上的单例模式)有一部分场景,要求对象的生命周期随着线程/** * 线程级单例模式 */public class ThreadLocalInstance { //静态的ThreadLocal类保存对象 private static final ThreadLocal<ThreadLocalInstance> threadLocal = ThreadLocal.withInitial(ThreadLocalInstance::new); private ThreadLocalInstance () {} public static ThreadLocalInstance getInstance () { return threadLocal.get(); }}通过getInstance()获取该线程的实例。0x05.单例模式的序列化与反射攻击1.序列化攻击以前面饿汉式举例测试代码public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.实例化 HungrySingleton instance = HungrySingleton.getInstance(); //2.写入本地文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); oos.writeObject(instance); //3.读取 File file = new File("singleton_file"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); HungrySingleton newInstance = (HungrySingleton) ois.readObject(); //4.比较 System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); }}输出结果org.ko.singleton.hungry.HungrySingleton@135fbaa4org.ko.singleton.hungry.HungrySingleton@568db2f2false解决方案:添加readResolve()方法修改后/** * 饿汉式 * 一开始就new好了 */public class HungrySingleton implements Serializable { private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton(); } public static HungrySingleton getInstance() { return hungrySingleton; } /** * 写完后,序列化对象会通过反射调用这个方法 * 完全是ObjectInputStream写死的,并没有任何继承关系 * 其实每次序列化 反序列化 都已经创建对象了,只是最后返回的这一个 * @return */ private Object readResolve () { return hungrySingleton; } private HungrySingleton() {}}输出结果org.ko.singleton.hungry.HungrySingleton@135fbaa4org.ko.singleton.hungry.HungrySingleton@135fbaa4true为什么添加了readResolve()方法就可以了? ...

June 8, 2019 · 4 min · jiezi

设计模式之建造者模式

0x01.定义与类型定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以得到他们,建造过程及细节不需要知道类型:创建型实现建造模式的两种方式1.抽象建造者UML: Java实现/*** 教练类,封装了具体构建的细节*/public class Coach { private AProductBuilder productBuilder; public void setProductBuilder(AProductBuilder productBuilder) { this.productBuilder = productBuilder; } public Product createProduct(String property) { productBuilder.buildProperty(property); return this.productBuilder.makeProduct(); }}/*** 抽象的构建,每添加新的构建方式,只需要继承这个抽象类即可*/public abstract class AProductBuilder { /** * 构建具体属性,只是mock一个,可以有很多个 * @param property */ abstract void buildProperty(String property); /** * 返回被构建的对象 * @return */ abstract Product makeProduct();}/*** 产品具体的构建类*/public class ProductActualBuilder extends AProductBuilder { private Product product = new Product(); @Override void buildProperty(String property) { product.setProperty(property); } @Override Product makeProduct() { return product; }}/*** 产品类*/public class Product { /** * 产品实现细节,可以很多个属性,这里只mock了一个 */ private String property; public void setProperty(String property) { this.property = property; } public String getProperty() { return property; }}测试应用类public class Test { public static void main(String[] args) { Coach coach = new Coach(); coach.setProductBuilder(new ProductActualBuilder()); Product product = coach.createProduct("测试属性"); System.out.println(product.getProperty()); }}输出结果测试属性抽象建造者模式中的元素 ...

June 7, 2019 · 4 min · jiezi

设计模式代理模式

一、写在前面代理模式是常用的结构型设计模式之一、当我们直接访问某些对象存在问题时可以通过代理模式来间接访问,为了保证客户端使用的透明性、所访问的真实对象和代理对象都必须实现同一个接口。 二、代理模式动机与定义某人要找对象(大部分是程序员),但是由于某些原因(996)不能直接去找。 于是就委托一个中介机构去帮自己完成找女朋友的过程,如婚姻中介所、某某社交软件等。 在这里婚姻介绍所或者某某社交软件就是一个代理,帮你找美女。 在我们生活中代理无处不在、比如房屋中介、职业中介(某某招聘网)等它们充当的都是一个代理角色。 所谓的代理就是一个人或者一个机构代表另一个人或者机构采取行动。 二.一 模式动机在某些情况下,一个客户不想或者不能直接应用另一个对象,可以通过一个称之为‘代理’的第三者来实现间接的引用。 代理对象在客户和目标对象之间起到一个中介的作用,并且可以通过代理对象去掉某些客户不能看到的内容或者服务,同时也可以添加客户需要的额外服务。 二.二 模式的定义代理模式(Proxy Patten)定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。 代理对象英文名称叫做 Peoxy 或者 Surrogate 他是一种对象结构模式。 三、代理模式结构与分析代理模式结构比较简单、其核心就是一个代理类,下面我们来分析下其模式结构 三.一 模式结构代理模式UML图 代理模式包含以下三个角色 Subject(抽象主题角色) 抽象主题角色申明了真实主题和代理主题的共同接口、这样以来任何使用真实主题的地方可以都使用代理主题、客户端需要针对抽象主题角色来编程Proxy(代理主题角色)代理主题角色包含了对真实主题的引用,从而可以在任何时候操作真实主题角色。RealSubject(真实主题角色)真实主题角色定义了代理主题角色所代表的具体对象,真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色来间接的调用真实主题角色中定义的方法。三.二模式分析代理模式的示意图结构比较简单、一般可以简化如下图所示。但是在现实中要复杂的多。 典型的代理类代码如下: public class Proxy implements Subject { private RealSubject realSubject = new RealSubject(); public void preRequest() { System.out.println("---pre---"); } public void request() { preRequest(); realSubject.request(); postRequest(); } public void postRequest() { System.out.println("----post-----"); }}在真实应用中,代理类的实现好比着复杂的多,它需要一套自己的方式去访问真实对象,以便作为真实对象的代理。 四、代理模式的实例代理模式在我应用开发中一般就分为静态代理和动态代理两类。下面我们来一个例子来具体的理解下代理模式 四.一 静态代理下面我们以我们最熟悉的Spring AOP 处理事务的方式来实现,废话不多说直接上代码吧! 抽象角色 public interface TrancationSubject { void request();}具体角色 ...

June 7, 2019 · 2 min · jiezi

设计模式之抽象工厂模式

0x01.定义与类型定义:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口无需指定它们具体的类类型:创建型UML 基本java实现/** * 工厂接口 */public interface IFactory { AProduct1 createProduct1(); AProduct2 createProduct2();}/** * 产品1 */public abstract class AProduct1 { public abstract void produce();}/** * 产品2 */public abstract class AProduct2 { public abstract void produce();}/** * 工厂实现 */public class FactoryImpl implements IFactory { @Override public ProductImpl1 createProduct1() { return new ProductImpl1(); } @Override public ProductImpl2 createProduct2() { return new ProductImpl2(); }}/** * 产品1实现 */public class ProductImpl1 extends AProduct1 { @Override public void produce() { System.out.println("产品1"); }}/** * 产品2实现 */public class ProductImpl2 extends AProduct2 { @Override public void produce() { System.out.println("产品2"); }}/** * 测试与应用 */public class Test { public static void main(String[] args) { FactoryImpl factory = new FactoryImpl(); ProductImpl1 productImpl1 = factory.createProduct1(); ProductImpl2 productImpl2 = factory.createProduct2(); productImpl1.produce(); productImpl2.produce(); }}输出结果产品1产品2抽象工厂中的元素 ...

June 6, 2019 · 2 min · jiezi

我对支付平台架构设计的一些思考

微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车!我在前一家公司的第一个任务是开发统一支付平台,由于公司的业务需求,需要接入多个第三方支付,之前公司的支付都是散落在各个项目中,及其不利于支付的管理,于是聚合三方支付,统一支付平台的任务就落在我手上,可以说是完全从 0 开始设计,经过一翻实战总结,我得出了一些架构设计上的思考,之前就一直很想把自己的架构设计思路写出来,但一直没动手,前几天在技术群里有人问到相关问题,我觉得有必要把它写出来,以帮助到更多需要开发支付平台的开发人员。 组件模式由于公司业务在很多地区都有,需要提供多种支付途径,以满足业务的发展,所以设计的支付平台需要接入多种第三方支付渠道,如:微信支付、支付宝支付、PayPal、IPayLinks 等等,我们都知道,每个第三方支付,都有自己一套对外 API,官方都有一套 SDK 来实现这些 API,我们应该如何组织这些 API 呢? 由于第三方支付渠道会随着业务的发展变动,所以组织这些 SDK 就需要在不影响支付平台整体架构的前提下可灵活插拔,这里我使用了组件的思想,将支付 API 拆分成各种组件支付组件、退款组件、订单组件、账单组件等等,那么这样就可以当引入一个第三方支付 SDK 时,可灵活在组件上面添加需要的 API,架构设计如下: 通过 Builder 模式根据请求参数构建对应的组件对象,将组件与外部分离,隐藏组件构建的实现。组件模式 + Builder 模式使得支付平台具备了高扩展性。 多账户体系在接入各种第三方支付平台,我们当时又遇到一个账户的问题,原因是公司当时的小程序与 APP 使用的是不同的微信账号,因此会出现微信支付会对应到多个账户的问题,而我当时设计支付平台时,没有考虑到这个问题,当时第三方支付只对应了一个账户,而且不同的第三方支付的账户之间相互独立且不统一。 于是我引入了多账户体系,多账户体系最重要的一个核心概念是以账户为粒度,接入多个第三方支付,统一账户的参数,构建了统一的支付账户体系,支付平台无需关心不同支付之间的账户差异以及第三方支付是否有多少个账户。 此时我在支付平台架构图加上账户层: 前端只需要传递 accountId,支付平台就可以根据 accountId 查询出对应的支付账户,然后通过 Builder 模式构建支付账户对应的组件对象,完全屏蔽不同支付之间的差异,在多账户体系里面,可以支持无限多个支付账户,完全满足了公司业务的发展需求。 统一回调与异步分发处理做过支付开发的同学都知道,目前的第三方支付都有一个特点,就是支付/退款成功后,会有一个支付/退款回调的功能,目的是为了让商户平台自行校验该笔订单是否合法,比如:防止在支付时,客户端恶意篡改金额等参数,那么此时支付成功后,订单会处于支付中状态,需要等待第三方支付的回调,如果此时收到了回调,在校验时发现订单的金额与支付的金额不对,然后将订单改成支付失败,以防止资金损失。回调的思想是只要保证最终的一致性,所以我们调起支付时,并不需要在此时校验参数的正确性,只需要在回调时校验即可。 讲完了回调的目的,那么我们如何来设计支付平台的回调呢? 由于支付平台接入了多个第三方支付,如果此时每个第三方支付设置一个回调地址,那么将会出现多个回调地址,由于回调的 API 必须是暴露出去才能接受第三方的回调请求,所以就会有安全问题,我们必须在 API 外层设置安全过滤,不然很容易出现一些非法暴力访问,所以我们需要统一回调 API,统一做安全校验,之后再进行一层分发。 分发的机制我这里建议用 RocketMQ 来处理,可能有人会问,如果用 RocketMQ 来做分发处理,此时怎么实时返回校验结果到第三方支付呢?这个问题也是我当时一直头疼的问题,以下是我对回调设计的一些思考: 公司的系统是基于 SpringCloud 微服务架构,微服务之间通过 HTTP 通信,当时有很多个微服务接入了我的支付平台,如果用 HTTP 作分发,可以保证消息返回的实时性,但也会出现一个问题,由于网络不稳定,就会出现请求失败或超时的问题,接口的稳定性得不到保障。由于第三方支付如果收到 false 响应,就在接下来一段时间内再次发起回调请求,这么做的目的是为了保证回调的成功率,对于第三方支付来说,这没毛病,但对于商户支付平台来说,也许就是一个比较坑爹的设计,你想一下,假设有一笔订单在支付时恶意篡改了金额,回调校验失败,返回 false 到第三方支付,此时第三方支付会再重复发送回调,无论发送多少次回调,都会校验失败,这就额外增加了不必要的交互,当然这里也可以用幂等作处理,以下是微信支付回调的应用场景说明: 基于以上两点思考,我认为返回 false 到第三方支付是没必要的,为了系统的健壮性,我采用了消息队列来做异步分发,支付平台收到回调请求后直接返回 true,这时你可能会提出一个疑问,如果此时校验失败了,但此时返回 true,会不会出现问题?首先,校验失败情况,订单必定是处于支付失败的状态,此时返回 true 目的是为了减少与第三方支付不必要的远程交互。 ...

June 6, 2019 · 1 min · jiezi

装饰者模式

装饰者模式装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。思维模式首先需要明确一点:装饰者模式利用了“组合”的思想,避免了“继承”的滥用。通过动态地组合对象,可以写新的代码添加新功能,而无需修改现有代码。 这就好比拼装一个机器人。我们有各种各样的零部件: 躯干类人的胳膊带有钳子的胳膊类人的腿带轮子的腿带履带的腿……简单粗暴的实现想要设计各式各样的机器人,当然我们可以选择,设计一个Robot超类,然后让子类继承这个超类。这样,我们可以得到: 躯干+人的胳膊+履带腿躯干+钳子胳膊+轮子腿躯干+4个人的胳膊+履带腿……根据排列组合的原理,如果我们需要,Robot的子类可以有无限多个。假如我们从Robot父类处继承了一个cost()方法,这个方法根据该子类的component(组件)个数和数量来计算成本(cost),那么我们的cost()方法可以说要重写无数次——因为每一个子类的情况都是不一样的。 “装饰者模式”的实现我们可以先为每一个零部件(component)确定成本,然后根据需要,动态地组装(组合)一个机器人。然后将所有的成本加起来。如此一来,不仅可以得到一个符合我们需求的机器人对象,更能很方便地计算cost。 躯干 $100类人的胳膊 $40带有钳子的胳膊 $50类人的腿 $70带轮子的腿 $70带履带的腿 $50……组合一个有躯干+类人的胳膊+带履带的腿的机器人的cost也就是: 100 + 40 + 50 = $190; 实现组合所谓组合,也就是我中有你。 如图所示: 通过保有各个对象的引用,即可实现“组合”。 对于这样一个组合来的Robot对象,我们不妨称其为:“robotTom”。想要求得总成本,可以直接调用robotTom.cost(); 这是怎样实现的呢?如图所示: 用公式来表示就是: [robotTom].cost() = [arm + body].cost() + leg.cost() = [body].cost() + arm.cost() + leg.cost() = body.cost() + arm.cost() + leg.cost();这里用“leg对象包住arm对象”来形容leg对象中有arm对象的引用,不过对于一个机器人而言,“leg能包住arm”好像有点魔幻现实的味道。 再举一例我们不妨再举一个更容易理解的例子: 我们知道,各类绘图软件中都有图层(layer)的概念。每一个图层都相当于一层透明纸,我们可以在上面任意地画东西,而不会影响其他图层。把画着东西的图层一层一层地叠放(相当于“包装”)起来,我们就可以得到各式各样的画作。此时,位于上一层的图层就相当于装饰者,而所谓的背景图层就相当于被装饰者。 类图其中, Layer、LayerDecorator为抽象类LayerDecorator本质上也是Layerdescription是各个layer对象的自我描述position()表示layer对象在某个地方画一个图形RedBackgroundLayer、BlueBackgroundLayer相当于被装饰者对应的代码:Layer.java: public abstract class Layer { String description = "我是抽象layer父类"; public String getDescription() { return description; } public abstract String position();}RedBackgroundLayer.java ...

June 5, 2019 · 2 min · jiezi

java-设计模式

设计模式分类创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。设计模式的六大原则单一职责原则不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 开闭原则(Open Close Principle)开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。 里氏代换原则(Liskov Substitution Principle)里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 依赖倒转原则(Dependence Inversion Principle)这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。 接口隔离原则(Interface Segregation Principle)这个原则的意思是:使用多个隔离的接口,比使用单个接口要好,还是一个降低类之间的耦合度的意思。 迪米特法则(最少知道原则)(Demeter Principle)一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

June 4, 2019 · 1 min · jiezi

设计模式之工厂方法模式

0x01.定义与类型定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化那个类,工厂方法让类的实例化推迟到子类中进行类型:创建型uml类图 代码//工厂接口public interface IFactory { IProduct createProduct();}//产品接口public interface IProduct { void produce();}//工厂实现public class Factory implements IFactory { @Override public IProduct createProduct() { return new Product(); }}//产品实现public class Product implements IProduct{ @Override public void produce() { System.out.println("具体产品业务。"); }}//应用 / 测试类public class Test { public static void main(String[] args) { IFactory factory = new Factory(); IProduct product = factory.createProduct(); product.produce(); }}应用 / 测试类输出结果具体产品业务。通过工厂方法模式的类图可以看到,工厂方法模式有四个要素: 工厂接口:工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品。在实际编程中,有时候也会使用一个抽象类来作为与调用者交互的接口,其本质上是一样的。工厂实现:在编程中,工厂实现决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。产品接口:产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。产品实现:实现产品接口的具体类,决定了产品在客户端中的具体行为。0x02.简单工厂对比工厂方法简单工厂只有三个要素,他没有工厂接口,并且得到产品的方法一般是静态的。因为没有工厂接口,所以在工厂实现的扩展性方面稍弱,可以算是工厂方法模式的简化版。简单工厂如果发生变动是需要修改工厂方法,违反了开闭原则。0x03.使用场景创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。总结的说 创建对象需要大量重复的代码客户端(应用层)不依赖于产品类实例如何被创建、实现等细节一个类通过其子类来指定创建那个对象(具有产品抽象)0x04.优点用户只需要关心所需要产品对应的工厂,无需关心创建细节加入新产品符合开闭原则,提高可扩展性0x05.缺点类的个数容易过多,增加复杂度增加了系统的抽象性和理解难度0x06.实现样例在线教育网站具有多种技术种类视频视频工厂与视频类的定义//视频工厂public abstract class VideoFactory { public abstract Video getVideo();}//视频类public abstract class Video { public abstract void produce();}Java视频的实现//Java视频工厂public class JavaVideoFactory extends VideoFactory { @Override public JavaVideo getVideo() { return new JavaVideo(); }}//Java视频public class JavaVideo extends Video { @Override public void produce() { System.out.println("录制Java课程视频"); }}Python视频实现//Python视频工厂public class PythonVideoFactory extends VideoFactory { @Override public PythonVideo getVideo() { return new PythonVideo(); }}//Python视频public class PythonVideo extends Video{ @Override public void produce() { System.out.println("录制Python课程"); }}前端课程实现//前端视频实现public class FEVideoFactory extends VideoFactory { @Override public FEVideo getVideo() { return new FEVideo(); }}//前端视频public class FEVideo extends Video { @Override public void produce() { System.out.println("录制前端视频"); }}应用与测试类实现public class Test { public static void main(String[] args) { VideoFactory videoFactory = new JavaVideoFactory(); Video video1 = videoFactory.getVideo(); video1.produce(); videoFactory = new PythonVideoFactory(); Video video2 = videoFactory.getVideo(); video2.produce(); videoFactory = new FEVideoFactory(); Video video3 = videoFactory.getVideo(); video3.produce(); }}输出结果录制Java课程视频录制Python课程录制前端视频实现UML类图 ...

June 2, 2019 · 1 min · jiezi

观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。这就好比一个办在线教育的老师(OnlineTeacher)和线下的若干学生(localStudent1, localStudent2, localStudent3 ...)之间的关系。 当这个老师在线上发布了一条消息之后,他所有的学生都会收到通知,并可以根据这条消息的内容来实现对应的更新。 观察者模式类图 为了实现松耦合设计,OnlineTeacher对象只需要实现Subject接口,并实现其中规定的三个方法即可: registerObserver(); //注册观察者removeObserver(); //取消注册观察者notifyObserver(); //通知观察者Subject接口:public interface Subject { //Subject接口中的三个方法,用于注册、移除和通知observers void registerObserver(Observer observer); void removeObserver(Observer observer); //以上两个方法都需要一个observer对象作为变量,以实现注册或被删除 void notifyObservers();}同时,OnlineTeacher对象还需要一个数据结构,来存储已经注册的学生对象。 OnlineTeacher类public class OnlineTeacher implements Subject { //建立一个数据结构来存储注册过的observer对象 private ArrayList<Observer> observerArrayList; //用message来模拟老师讲课 private String message; //OnlineTeacher类的构造器,生成该对象时,会初始化一个observerArrayList public OnlineTeacher() { observerArrayList = new ArrayList<>(); } @Override public void registerObserver(Observer observer) { observerArrayList.add(observer); } @Override public void removeObserver(Observer observer) { int i = observerArrayList.indexOf(observer); if (i > 0) { observerArrayList.remove(i); } } //实现notifyObservers(),实质上就是遍历observerArrayList,让其中的每一个observer对象调用他们的update()方法 @Override public void notifyObservers() { for (int i = 0; i < observerArrayList.size(); i++) { observerArrayList.get(i).updates(message); } } //OnlineTeacher有一个方法,setMessage() public void setMessage(String newMessage) { this.message = newMessage; messageChanged(); //message赋值之后,调用messageChanged()方法 } //还有一个方法,messageChanged,在此方法中调用notifyObservers() public void messageChanged() { notifyObservers(); }}而所有的学生对象只需要实现Observer接口即可成为“观察者”。所有的观察者只需要保有一个OnlineTeacher对象的引用,便可以在各自的构造器中实现对自身的注册。 ...

May 31, 2019 · 2 min · jiezi

设计模式之简单工厂模式

0x01.定义与类型定义:由一个工厂对象决定创建出哪一种产品类的实例。类型:创建型,但不属于GOF23中设计模式。简介:通过一个专门的工厂类来创建其他类,这些被创建的类通常有一个共同的父类或接口。0x02.适用场景工厂类负责创建的对象比较少客户端(应用层)只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心0x03.简单工厂的优缺点优点:只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节缺点:工厂类的职责相对过重,增加新的产品,需要修改工厂类的判断逻辑,违背开闭原则0x04.简单工厂的实线样例假设IT教育课程,具有Java, Python等视频。他们都是视频类的子类,而有单独的视频工厂生产这些课程!课程基类public abstract class Video { public abstract void produce();}视频工厂public class VideoFactory { /** * 每次新增加产品实线会修改代码 * 不符合开闭原则 * @param type * @return */ public Video getVideo(String type) { if ("java".equalsIgnoreCase(type)) { return new JavaVideo(); } else if ("python".equalsIgnoreCase(type)) { return new PythonVideo(); } else return null; } /** * 反射方式实现 * @param clazz * @return */ public Video getVideo(Class clazz) { Video video = null; try { video = (Video) Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return video; }}Java, Python视频的具体实线类public class JavaVideo extends Video { @Override public void produce() { System.out.println("录制Java课程视频"); }}public class PythonVideo extends Video{ @Override public void produce() { System.out.println("录制Python课程"); }}最后的测试类,测试视频工厂输出的视频public class Test {// public static void main(String[] args) {// Video video = new JavaVideo();// video.produce();// } public static void main(String[] args) { VideoFactory videoFactory = new VideoFactory(); Video video1 = videoFactory.getVideo("python"); if (video1 != null) { video1.produce(); } Video video2 = videoFactory.getVideo("java"); if (video2 != null) { video2.produce(); } } /* public static void main(String[] args) { VideoFactory videoFactory = new VideoFactory(); Video video = videoFactory.getVideo(PythonVideo.class); if (video != null) { video.produce(); } }*/}输出结果录制Python课程录制Java课程视频0x05.UML类图 ...

May 31, 2019 · 2 min · jiezi

设计模式之建造者设计模式

这是设计模式系列的第二篇——建造者设计模式,我希望推送的文章是一个系列的,尽量保持一样的写作风格,尽量把我理解的阐述清楚,关于建造者设计模式主要从以下几个方面来学习,具体如下: 概述本质关键概念具体实现总结概述建造者设计模式(Builder Pattern)属于创建型设计模式,主要用于创建复杂的对象,可将复杂对象的构建过程抽象出来,通过不同实现的构建者和装配者最终组装出不同的对象,可以非常方便的增加不同实现的构建者、组装者而不用修改以前的代码。 本质建造者设计模式(Builder Pattern)分离了对象子组件的构造过程和组装过程,实现了构建与组装的解耦,不同的构建器相同的组装顺序以及相同的构建器不同的组装顺序都可以创建出不同的对象,使得构建与组装充分解耦,进而实现构建算法与组装算法的解耦,从而实现更好的复用。 关键概念构建者(Builder):构建不同的子组件且返回子组件或者提供获取复杂对象的方法,将构建过程抽象成接口或抽象类,方便扩展具体的不同的构建者。组装者(Dirctor):通过某个具体的构建者构建相关的子组件,同时对外提供组成复杂产品对象的方法。当需要生成复杂对象时,直接通过某个具体的组装者获得想要的具体对象即可,至于组装过程与构建过程使用者不需要关心,分别由具体的组装者与具体的构建者内部完成。当然复杂对象可以理解为具有很多属性的对象。 具体实现下面以手机的组装过程来说明建造者设计模式的具体实现,产品类如下: /** * 产品 * @author jzman */public class Phone { private Screen screen; private Camera camera; private Cpu cpu; //省略getter、setter、toString 方法 //...}//子组件class Screen{ private String name; //...}//子组件class Camera{ private String name; //...}//子组件class Cpu{ private String name; //...}抽象的构建者: /** * 构建者 * @author jzman */public interface PhoneBuilder { Screen builderScreen(); Camera builderCamera(); Cpu builderCpu();}具体的构建者: /** * 具体的构建者 * @author jzman */public class MiPhoneBuilder implements PhoneBuilder{ @Override public Screen builderScreen() { System.out.println("构建屏幕..."); return new Screen("Mi-screen"); } @Override public Camera builderCamera() { System.out.println("构建相机..."); return new Camera("Mi-camera"); } @Override public Cpu builderCpu() { System.out.println("构建屏幕..."); return new Cpu("Mi-cpu"); }}抽象的组装者: ...

May 31, 2019 · 1 min · jiezi

设计模式之软件设计七大原则

0x01.开闭原则定义:一个软件实体如类,模块和函数应该对扩展开放,对修改关闭要点: 当变更发生时,不要直接修改类,而是通过继承扩展的方式完成变更用抽象构建框架,用实现扩展细节预先设计好抽象优点:提高软件系统的可复用性及可维护性面向抽象编程,实现抽象化,抽象出业务模型提高内聚,降低耦合样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/openclose0x02.依赖倒置原则定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象 这个设计原则比较好理解,首先高层模块只接口或者父类,接口或父类不该依赖其派生子类抽象不应该依赖细节;细节应该依赖抽象。针对接口编程,不要针对实现编程。优点:可以减少类间的耦合性,提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/dependenceinversion0x03.单一职责原则定义:不要存在多余一个导致类变更的原因,就一个类而言,应该只存在一个导致其变更的原因。一个类/接口/方法只负责一项职责降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/singleresponsibility0x04.接口隔离原则用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖它不需要的接口一个类对一个类的依赖应该建立在最小的接口上建立单一接口,不要建立庞大臃肿的接口尽量细化接口,接口中的方法尽量少注意适度原则,一定要适度,否则会造成类爆炸优点:符合我们常说的高内聚低耦合的设计思想,从而使类具有很好的可读性,可扩展性和可维护性样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/interfacesegregation0x05.迪米特法则(最少知道原则)定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则尽量降低类与类之间的耦合降低类之间的耦合强调只和朋友交流,不和陌生人说话朋友:出现在成员变量,方法的输入,输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。类与类之间的引用,尽量只是直接关系的引用,避免不必要的耦合。样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/demeter0x06.里氏替换原则定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。定义扩展:一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法含义2:子类中可以增加自己特有的方法含义3:当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。含义4:当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。优点1:约束继承泛滥,开闭原则的一种体现。优点2:加强程序的健壮性,同事变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/liskovsubstitution0x07.合成/复用原则(组合/复用原则)定义:尽量适用对象组合/聚合,而不是继承关系达到软件复用的目的聚合has-A和组合contains-A优点:可以适用系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少何时适用合成/聚合,继承聚合has-A, 组合contains-A, 继承 is-A样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/compositionaggregation0x08.代码工程七大设计原则: https://github.com/sigmako/design-pattern/tree/master/design-principle 0x09.参考慕课网设计模式精讲: https://coding.imooc.com/class/270.html

May 30, 2019 · 1 min · jiezi

JS设计模式学习基础篇

一、写在前面设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案当然我们可以用一个通俗的说法:设计模式是解决某个特定场景下对某种问题的解决方案。因此,当我们遇到合适的场景时,我们可能会条件反射一样自然而然想到符合这种场景的设计模式。 比如,当系统中某个接口的结构已经无法满足我们现在的业务需求,但又不能改动这个接口,因为可能原来的系统很多功能都依赖于这个接口,改动接口会牵扯到太多文件。因此应对这种场景,我们可以很快地想到可以用适配器模式来解决这个问题。 以上参考自网易考拉前端团队-JavaScript设计模式 二、设计原则设计哲学准则: 小即是美让每个程序只做好一件事快速建立原型舍弃高效率而取可移植性采用纯文本来存储数据充分利用软件的杠杆效应(可复用)使用shell脚本来提高杠杆效应和可移植性避免强制性的用户界面每个程序都称为过滤器小准则: 允许用户定制环境尽量使操作系统内核小而轻量化使用小写字母且简写 list = ls沉默是金各部分之和大于整体寻求90%的解决方案三、SOLID设计原则S 单一职责原则 single 一个程序只做好一件事情如果功能复杂就拆分开,每个部分保持独立O 开放封闭原则 open 对扩展开放,对修改封闭增加需求时,扩展新需求,而非修改已有代码这是软件设计的终极目标L 李氏置换原则 子类能覆盖父类父类能出现的地方子类就能出现JS使用较少I 接口独立原则 保持接口独立,避免出现“胖接口”JS中没有接口(ts)D 依赖导致原则 面向接口编程,依赖于抽象而不依赖于具体使用方只关心接口而不关心具体类的实现JS使用较少四、23种设计模式创建型设计模式是一类处理对象创建的设计模式,通过某种方式控制对象的创建来避免基本对象创建时可能导致设计上的问题或增加设计上的复杂度。工厂模式、单例模式结构型设计模式关注于如何将类或对象组合成更大、更复杂的结构,以简化设计。适配器模式、装饰器模式、代理模式、外观模式行为型设计模式用于不同对象之间职责划分或者算法抽象,行为型设计模式不仅仅涉及类和对象,还涉及类或对象之间的交流模式并加以实现。观察者模式、迭代器模式、状态模式五、UML类图UML(Unified Modeling Language)是一种统一建模语言,为面向对象开发系统的产品进行说明、可视化、和编制文档的一种标准语言。5.1 类图的表示类图分三层,第一层显示类的名称,如果是抽象类,则就用斜体显示。第二层是类的特性,通常就是字段和属性。第三层是类的操作,通常是方法或行为。前面的符号,+ 表示public,- 表示private,# 表示protected(js中为严格区分,ts中有) 那么如何根据类图写出相应的代码结构呢?如下: 注:默认不添加属性或方法类型,即为 public,因此 public 可省略 5.2 类关系表示泛化关系【继承】空心箭头表示,是一种继承关系。例如:自行车是车聚合关系空心菱形箭头表示,是整体与部分的关系,与组合关系不同,整体和部分不是强依赖的。例如,部门撤销了,人员不会消失,他们依然存在组合关系实心菱形箭头表示,是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门关联关系【引用】实线(可带单/双箭头)表示,是一种拥有的关系,它使一个类知道另一个类的属性和方法 除了上述类关系外,还有实现关系,依赖关系等表示法,可参考下面博文:看懂UML类图和时序图UML 各种图总结精华 六、真题1 根据下面的信息画UML类图 打车时,可以打快车或者专车。任何车都有车牌号和名称不同车打车价格不同,快车1元/公里,专车2元/公里行程开始时显示车辆信息行程结束时显示打车金额2 根据下面的信息画UML类图 某停车场,分三层,每层100车位每个车位都能监控到车辆的驶入和离开车辆驶入前,显示每层空余车位数量车辆驶入时,摄像头可识别车牌号和时间车辆出来时,出口显示器显示车牌号和停车时间 分析: 车是一个大类,快车和专车继承自车行程是一个单独的类,连接用车和开始结束两个动作 分析: 关系1:车位组成层,层组成车库。组合关系关系2:摄像机和显示屏是车库的部分,且能单独存在。聚合关系车停入车位时,需要判别该车位状态(是否为空车位)车库需要记录车驶入驶出的状态和记录车位数,需要通过层获取

May 29, 2019 · 1 min · jiezi

面向对象基本原则3-最少知道原则与开闭原则

面向对象基本原则(3)- 最少知道原则与开闭原则五、最少知道原则【迪米特法则】1. 最少知道原则简介最少知识原则(Least KnowledgePrinciple,LKP)也称为迪米特法则(Law of Demeter,LoD)。虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。 通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。 2. 最少知道原则实现只与直接关联的类交流每个对象都必然会与其他对象有耦合关系,耦合关系的类型有很多,例如组合、聚合、依赖等。 出现在成员变量、方法的输入输出参数中的类称为直接关联的类,而出现在方法体内部的类不属于直接关联的类。 下面举例说明如何才能做到只与直接关联的类交流。 场景:老师想让班长清点女生的数量 Bad/** * 老师类 * Class Teacher */class Teacher { /** * 老师对班长发布命令,清点女生数量 * @param GroupLeader $groupLeader */ public function command(GroupLeader $groupLeader) { // 产生一个女生群体 $girlList = new \ArrayIterator(); // 初始化女生 for($i = 0; $i < 20; $i++){ $girlList->append(new Girl()); } // 告诉班长开始执行清点任务 $groupLeader->countGirls($girlList); }}/** * 班长类 * Class GroupLeader */class GroupLeader { /** * 清点女生数量 * @param \ArrayIterator $girlList */ public function countGirls($girlList) { echo "女生数量是:", $girlList->count(), "\n"; }}/** * 女生类 * Class Girl */class Girl {}$teacher= new Teacher();//老师发布命令$teacher->command(new GroupLeader()); // 女生数量是:20上面实例中,Teacher类仅有一个直接关联的类 -- GroupLeader。而Girl这个类就是出现在commond方法体内,因此不属于与Teacher类直接关联的类。方法是类的一个行为,类竟然不知道自己的行为与其他类产生依赖关系,这是不允许的,违反了迪米特法则。 ...

May 27, 2019 · 5 min · jiezi

设计模式之UML类图

类图(Class diagram)主要用于描述系统的结构化设计。类图也是最常用的UML图,用类图可以显示出类、接口以及它们之间的静态结构和关系。0x01.类图中的元素1.类 Class / 接口 Interface 第一格:表示类的名字,抽象类用斜体表示,接口在前面加<interface>第二格:表示类的属性,前面符号表示作用于,冒号后面表示类的类型符号解释+public-private#protected~default下面有横线表示static静态属性第三格:表示类的行为,同上符号解释+-#~同上斜体抽象方法下面有横线表示static静态方法:String:后表示返回值,返回String字符串类型无:表示void无返回2.UML类图中各个类的关系 a).依赖关系一个类需要借助另一个类来实现功能是依赖关系,使用虚线和箭头,箭头指向被依赖的对象b).泛化继承关系空心箭头实线,箭头方向是子类指向父类来表示,只有知道对方信息才能指向对方c).组合关系实心菱形和大于箭头,实线。组合关系可用在两侧写上数字,表示一只鸟两个翅膀。组合关系对象同样生命周期d).关联关系表示一个类和另外一个类是有关联的,一般有一个属性是另一个类,用实线和大于箭头,指向被关联对象e).聚合关系空心菱形和大于箭头。整体和局部的关系,两者拥有独立的生命周期,是has-a的关系。空心菱形是可以放东西的盘子,可以用来装东西。东西放在盘子里就可以装进去f).实现关系空心三角和虚线,指向接口g).接口表示棒棒糖表示法3.相似关系比较a).依赖关系和关联关系依赖关系:一般实现在方法上,不调用方法不需要使用,虚无缥缈,虚线关联关系:是属性依赖,很关键,实打实的b).聚合关系和组合关系,空心和实心菱形菱形的方向,数量少的是菱形的方向聚合是一个盘子可以称很多东西,但是他们是相同的,只是数量不同,不同的生命周期组合的生命周期是相同的c).继承和实现继承:类和类之间的关系,很实实现:限制的不明显,很虚只有知道对方信息,才可以指向

May 26, 2019 · 1 min · jiezi

面向对象基本原则2-里式代换原则与依赖倒置原则

面向对象基本原则(2)- 里式代换原则与依赖倒置原则三、里式代换原则0. 继承的优缺点在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点: 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。提高代码的重用性。子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同。提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;提高产品或项目的开放性。自然界的所有事物都是优点和缺点并存的,继承的缺点如下: 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束。增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。1. 里式代换原则简介里式代换原则的英文名称是 Liskov Substitution Principle,简称LSP。 里式代换原则的英文定义是: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.意思是:所有引用基类的地方必须能透明地使用其子类的对象。 通俗点讲,就是只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。 2. 里氏替换原则为良好的继承定义了规范子类必须完全实现父类的方法/** * 枪支抽象类 * Class AbstractGun */abstract class AbstractGun{ public abstract function shoot();}/** * 手枪 * Class Handgun */class Handgun extends AbstractGun{ public function shoot() { echo "手枪射击\n"; }}/** * 步枪 * Class Rifle */class Rifle extends AbstractGun{ public function shoot() { echo "步枪射击\n"; }}如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承。 ...

May 25, 2019 · 3 min · jiezi

面向对象基本原则1-单一职责原则与接口隔离原则

面向对象基本原则(1)- 单一职责原则与接口隔离原则一、单一职责原则1. 单一职责原则简介单一职责原则的英文名称是 Single Responsibility Principle,简称SRP。 单一职责原则的原话解释是: There should never be more than one reason for a class to change.意思是:应该有且仅有一个原因引起类的变更。 2. 单一职责原则优点类的复杂性降低,实现什么职责都有清晰明确的定义。可读性提高,复杂性降低,那当然可读性提高了。可维护性提高,可读性提高,那当然更容易维护了。变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。3. 最佳实践接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。 注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。4. Show me the code代码使用PHP7.2语法编写用户业务场景IUserBo 接口负责用户属性 interface IUserBo{ public function setUserID(string $userID); public function getUserID() : string ; public function setPassword(string $password); public function getPassword() : string ; public function setUserName(string $userName); public function getUserName() : string ;}IUserBiz 接口负责用户行为 interface IUserBiz{ public function changePassword(string $password) : bool ; public function deleteUser(IUserBo $userBo) : bool ; public function mapUser(IUserBo $userBo); public function addOrg(IUserBo $userBo, int $orgID) : bool; /** * 给用户添加角色 * @param IUserBo $userBo * @param int $roleID * @return bool */ public function addRole(IUserBo $userBo, int $roleID) : bool ;}UserInfo 类实现 IUserBo, IUserBiz 两个接口 ...

May 23, 2019 · 2 min · jiezi

代理模式

代理简单介绍生活中的代理:黄牛 【三个角色。我(真实对象)黄牛(代理对象)火车票(目的对象)】代理模式出现的原因真实对象无法直接访问目的对象真实对象不想直接访问目的对象真实对象访问目的对象存在困难解决的问题是:在直接访问对象时带来的问题 ,原先直接访问的两个类通过代理对象不在直接访问,可以实现解耦。代理模式对应的UML图图片描述 代理模式的分类按照代理对象的创建时机划分为两种:1、在编译前已经创建好对象,编译之后运行前代理类的.Class文件就已经存在,这种的称为静态代理2、程序在运行的时候动态的创建代理对象,这种的称为动态代理 静态代理在代码中手动的创建代理类。模拟的代码如 IDEA展示 /** * 定义一个接口 * 真实对象要干什么,代理对象通过接口可以知道 */public interface IUserDo { void takeTrain(Ticket ticket);}/** * @author pangjianfei * @Date 2019/5/22 * @desc 真实对象 */public class RealUser implements IUserDo { public final String NAME = "真实回家人"; @Override public void takeTrain(Ticket ticket) { System.out.println(NAME + " : 抢到票了,我要回家了,花了"+ ticket.proxyPrice.intValue() + "元,值了!"); }}/** * @author pangjianfei * @Date 2019/5/22 * @desc 代理角色,黄牛 */public class ProxyUser implements IUserDo{ public final String NAME = "黄牛"; /**代理的是哪一位*/ RealUser realUser; public ProxyUser(RealUser realUser) { this.realUser = realUser; } @Override public void takeTrain(Ticket ticket) { Ticket realTicket = buyTicket(ticket); realUser.takeTrain(realTicket); System.out.println(NAME + " : 我挣了" +(realTicket.proxyPrice.intValue()-realTicket.realPrice.intValue()) + "元"); } public Ticket buyTicket(Ticket ticket) { System.out.println(NAME + " : 去买从"+ticket.from+"到"+ticket.to+"的火车票了"); Ticket backHomeTicket = new Ticket(); backHomeTicket.setFrom(ticket.from); backHomeTicket.setTo(ticket.to); backHomeTicket.setTakeOffDate(ticket.takeOffDate); backHomeTicket.setRealPrice(new BigDecimal(100)); backHomeTicket.setProxyPrice(new BigDecimal(120)); System.out.println(NAME + " : 购买成功"); return backHomeTicket; }}

May 22, 2019 · 1 min · jiezi

设计模式使用心得

写在前面作为一名有追求的程序员,在完成项目的前提下,应该总是希望能够编写出便于阅读、便于扩展、结构良好的代码,简单概括的话,就是编写出优雅的代码。 一些团队在编码规范上,只要求了工程结构分层,并没有对如何编写优雅的代码进行更多的要求。 举个例子,团队可能要求开发Web程序时,工程有如下分层: Controller:编写控制器;Business:编写业务;Dao:编写数据库访问;Entity:编写实体类;Util:编写工具类;其中,以Business为例,团队并没有约定Business中的那些业务实现类如何设计。也许在大团队中,会有专职的架构师来对业务进行需求分析、绘制UML图、编写概要设计、详细设计。但多数团队并没有这样好的条件,更多的时候,是由架构师负责定义好技术架构,研发工程师负责进行需求实现(与业务人员或产品经理对接)。 如果是后面一种情况(研发直接负责需求实现),软件的可扩展性就完全落到研发的身上。 有些团队会因为时间紧张,任务繁重而放弃程序的扩展性,举个例子:一个功能模块编写一个业务实现类。如果是一个长期维护的项目,放弃扩展性可能会在首次研发阶段节省一点时间,但就长远来看,要付出的代价有可能会更多。 所以,如果在编写业务代码时,能自然而然的考虑扩展性,这样才能算是一名合格的软件工程师吧? 因此,我想把我从书本中学到的知识、从实战中收获的经验,进行一次全面的总结,希望自己能从这次总结中学习到更多,也希望能总结中的某一点,能对其他同行起到帮助。 参考书籍《Head First设计模式》《图解设计模式》《设计模式之禅》《领域驱动设计》《重构》《Effective Java》

May 22, 2019 · 1 min · jiezi

UI-设计中的渐变

简评:渐变是通过两种或多种不同的色彩来绘制一个元素,同时在颜色的交界处进行衰减变化的一种设计。从拟物到扁平再到渐变,人们慢慢发现它能创造出从未有过的一种色彩感觉 —— 独特、现代和清爽。(本文译者@Aceyclee)很长一段时间以来,设计界都不怎么待见「渐变」,这还得拜 90 年代的 PowerPoint 所赐,毕竟那时候的渐变是这样的: 然而时过境迁,今天你打开 Dribbble 或者 Behance,可能会看到不少使用渐变的设计。本文将会分享如何在设计中使用「渐变」的实用技巧。 ▎为什么我们突然爱上了渐变?想回答这个问题,我们需要重返 2014。 2014 年是扁平设计流行的元年。这一年,Google 发布了 Material Design,Apple 也用扁平 UI 更新了 macOS。那年的扁平设计让人感到兴奋,特别是和拟物设计对比的时候。 但是扁平设计的局限性也是显而易见的,其中最大的一个局限是 —— 设计师能够使用的颜色和样式大大减少,几乎不到 15 种颜色可用于扁平设计。 由此,设计师们开始尝试「渐变」: 它给设计师带来了更大的创作自由。扁平设计中单一的颜色扼杀了设计的潜力,而渐变为无限可能打开了大门。通过混合颜色的手段,设计师们可以创造出更丰富的视觉样式。渐变还给设计引入了深度和维度,解决了扁平设计中一切都「太平」了的问题。▎渐变让人惊喜比如说,渐变能做出更大胆的表达。 渐变创造出了一种意想不到的效果,即使像应用图标这样微小的元素,也别有一番风味: 又或者,渐变能突出某些元素。 一个好的用户体验,往往能引导用户完成产品流程,而精心的设计有助于让用户下意识地进行下一步。比方说让页面某些部分视觉效果更强烈,从而让用户更关注这部分。 ????下面是 Mixpanel 的主页,插图是主页的一大元素,而渐变的背景则引导了用户去关注镇部分。 ▎六种创造渐变的技巧1. 让我们先看看渐变的基础类型渐变有几种基础类型,他们都会有一个中心起点,颜色从这里开始,然后逐渐融入其他颜色。 a. 线性 线性渐变是一种依直线过渡的色带,颜色从一种颜色平滑过渡到下一种。 b. 径向 径向渐变是从中心点开始往外辐射,设计师可以指定中心点的位置和渐变的大小。 c. 锥形渐变 如下图,你可以在 Adobe Illustrator 中创建锥形渐变 2. 注意渐变颜色的选取不是所有的颜色都能很好地相互配合,如果用互补色进行渐变,看起来就不是很好: 红色到绿色的渐变,看起来脏兮兮的: 创建渐变的时候,最好使用相近的颜色: 或者相同颜色的不同深度: 3. 渐变过程中注意平滑过渡理想情况下,不应该让用户注意到一种颜色流入到另一种颜色中,颜色的过渡应该非常顺畅。 ...

May 21, 2019 · 1 min · jiezi

Java-设计模式综合运用门面模版方法责任链策略工厂方法

在上一篇文章Java设计模式综合运用(门面+模版方法+责任链+策略)中,笔者写了一篇门面模式、模版方法、责任链跟策略模式的综合运用的事例文章,但是后来笔者发现,在实现策略模式的实现上,发现了一个弊端:那就是如果在后续业务发展中,需要再次增加一个业务策略的时候,则需要再次继承AbstractValidatorHandler类(详情请参见上篇文章),这样就会造成一定的类膨胀。今天我利用注解的方式改造成动态策略模式,这样就只需要关注自己的业务类即可,无需再实现一个类似的Handler类。本文也同步发布至简书,地址:https://www.jianshu.com/p/b86...1. 项目背景1.1 项目简介在公司的一个业务系统中,有这样的一个需求,就是根据不同的业务流程,可以根据不同的组合主键策略进行动态的数据业务查询操作。在本文中,我假设有这样两种业务,客户信息查询和订单信息查询,对应以下枚举类: /** * 业务流程枚举 * @author landyl * @create 11:18 AM 05/07/2018 */public enum WorkflowEnum { ORDER(2), CUSTOMER(3), ; ....}每种业务类型都有自己的组合主键查询规则,并且有自己的查询优先级,比如客户信息查询有以下策略: customerIdrequestIdbirthDate+firstName以上仅是假设性操作,实际业务规则比这复杂的多 1.2 流程梳理主要业务流程,可以参照以下简单的业务流程图。 1.2.1 查询抽象模型 1.2.2 组合主键查询策略 1.2.3 组合主键查询责任链 2. Java注解简介注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致。 2.1 元注解JDK1.5提供了4种标准元注解,专门负责新注解的创建。 注解说明@Target表示该注解可以用于什么地方,可能的ElementType参数有:<br/>CONSTRUCTOR:构造器的声明<br/>FIELD:域声明(包括enum实例)<br/>LOCAL_VARIABLE:局部变量声明<br/>METHOD:方法声明<br/>ACKAGE:包声明<br/>PARAMETER:参数声明<br/>TYPE:类、接口(包括注解类型)或enum声明@Retention表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:<br/>SOURCE:注解将被编译器丢弃<br/>CLASS:注解在class文件中可用,但会被VM丢弃<br/>RUNTIME:JVM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。@Document将注解包含在Javadoc中@Inherited允许子类继承父类中的注解2.2 自定义注解定义一个注解的方式相当简单,如下代码所示: @Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documented//使用@interface关键字定义注解public @interface Description { /* * 注解方法的定义(其实在注解中也可以看做成员变量)有如下的规定: * 1.不能有参数和抛出异常 * 2.方法返回类型只能为八种基本数据类型和字符串,枚举和注解以及这些类型构成的数组 * 3.可以包含默认值,通过default实现 * 4.如果只有一个方法(成员变量),最好命名为value */ String value(); int count() default 1; //默认值为1}注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。 2.3 使用注解如上所示的注解使用如下: /** * @author landyl * @create 2018-01-12:39 PM *///在类上使用定义的Description注解@Description(value="class annotation",count=2)public class Person { private String name; private int age; //在方法上使用定义的Description注解 @Description(value="method annotation",count=3) public String speak() { return "speaking..."; }}使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。 ...

May 18, 2019 · 2 min · jiezi

Java设计模式综合运用门面模版方法责任链策略

引言:很久没有更新了,主要是工作忙。最近,工作中一个子系统升级,把之前不易扩展的缺点给改进了一下,主要是运用了几个设计模式进行稍微改造了一下。本文也同步发布至简书,地址: https://www.jianshu.com/p/962...1.项目背景在公司的一个实际项目中,需要做一个第三方公司(以下简称GMG)的系统集成工作,把该公司的一些订单数据集成到自己公司平台下,各个订单具有一些共性,但是也有其特有的特征。 经过设计,目前我把订单分为POLICY和BOB类型(暂且这么说吧,反正就是一种订单类型,大家参照着看就OK)。 在订单数据集成到公司平台前,需要对订单数据进行一些必要的业务逻辑校验操作,并且每个订单都有自己的校验逻辑(包含公共的校验逻辑)。 本节介绍的便是整个订单集成系统中的校验逻辑在综合利用设计模式的基础上进行架构设计。 2.校验逻辑本校验逻辑主要分为四个部分: 校验文件名称(RequestValidator.validateFileInfo)校验文件内容中的概要部分(RequestValidator.validateSummary)校验文件内容中的列名称(RequestValidator.validateHeaders)校验文件内容中的明细(RequestValidator.validateDetails)其实上面的RequestValidator的实现逻辑最后都是委托给RequestValidationFacade这个门面类进行相应的校验操作。 3.实现细节3.1 domain介绍主要分为RequestFile和RequestDetail两个domain,RequestFile接收泛型的类型(即RequestFile), 使得其子类能够自动识别相应的RequestDetail的子类。RequestFile为抽象类,定义了以下抽象方法,由子类实现: //由子类实现具体的获取文件明细内容public abstract List<T> getRequestDetails();//由子类实现具体的获取workflow的值public abstract WorkflowEnum getProcessWorkFlow();//由子类实现文件列字段名列表public abstract String[] getDetailHeaders();RequestDetail及其子类就是workflow对应文件的明细内容。 3.2 WorkflowEnum枚举策略本例中如下规定: workflow为WorkflowEnum.POLICY对应文件名为:csync_policy_yyyyMMdd_HHmmss_count.txtworkflow为WorkflowEnum.BOB对应文件名为:csync_bob_integration_yyyyMMdd_HHmmss_count.txt以上校验逻辑在AbstractRequestValidation类相应的子类中实现(validateFileName方法),其实这个枚举贯穿整个校验组件,它就是一个针对每个业务流程定义的一个枚举策略。 3.3 涉及到的设计模式实现思路3.3.1 门面模式在客户端调用程序中,采用门面模式进行统一的入口(门面模式讲究的是脱离具体的业务逻辑代码)。门面模式封装的结果就是避免高层模块深入子系统内部,同时提供系统的高内聚、低耦合的特性。 此案例中,门面类为RequestValidationFacade,然后各个门面方法的参数均为抽象类RequestFile,通过RequestFile->getProcessWorkFlow()决定调用AbstractRequestValidation中的哪个子类。 AbstractRequestValidation类构造方法中定义了如下逻辑: requestValidationHandlerMap.put(this.accessWorkflow(),this.accessBeanName());把子类中Spring自动注入的实体bean缓存到requestValidationHandlerMap中,key即为WorkflowEnum枚举值,value为spring bean name, 然后在门面类中可以通过对应的枚举值取得BeanName,进而得到AbstractRequestValidation相应的子类对象,进行相应的校验操作。 注:这边动态调用到AbstractRequestValidation相应的子类对象,其实也是隐藏着【策略模式】的影子。 类图如下: 3.3.2 模版方法模式在具体的校验逻辑中,用到核心设计模式便是模版方法模式,AbstractRequestValidation抽象类中定义了以下抽象方法: /** * validate the file details * @param errMsg * @param requestFile * @return */ protected abstract StringBuilder validateFileDetails(StringBuilder errMsg,RequestFile requestFile); /** * validate the file name * @param fileName * @return */ protected abstract String validateFileName(String fileName); /** * return the current CSYNC_UPDATE_WORKFLOW.UPDATE_WORKFLOW_ID * @return */ protected abstract WorkflowEnum accessWorkflow(); /** * return the current file name's format ,such as: csync_policy_yyyyMMdd_HHmmss_count.txt * @return */ protected abstract String accessFileNameFormat(); /** * return the subclass's spring bean name * @return */ protected abstract String accessBeanName();以上抽象方法就类似我们常说的钩子函数,由子类实现即可。类图如下图所示: ...

May 18, 2019 · 2 min · jiezi

Java设计模式综合运用动态代理Spring-AOP

本文也同步发布至简书,地址:https://www.jianshu.com/p/f70...AOP设计模式通常运用在日志,校验等业务场景,本文将简单介绍基于Spring的AOP代理模式的运用。 1. 代理模式1.1 概念代理(Proxy)是一种提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。 1.2 静态代理静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。 1.3 动态代理1.3.1 JDK代理JDK动态代理有以下特点:1.代理对象,不需要实现接口2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)3.动态代理也叫做:JDK代理,接口代理 1.3.2 CGLib代理Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。 JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。2. Spring AOP2.1 Spring AOP原理AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。本文以Spring AOP的实现进行分析和介绍。 Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。 Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 注意:以上片段引用自文章Spring AOP的实现原理,如有冒犯,请联系笔者删除之,谢谢!Spring AOP判断是JDK代理还是CGLib代理的源码如下(来自org.springframework.aop.framework.DefaultAopProxyFactory): @Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); }}由代码发现,如果配置proxyTargetClass = true了并且目标类非接口的情况,则会使用CGLib代理,否则使用JDK代理。 ...

May 18, 2019 · 3 min · jiezi

工厂模式二之建造者模式

紧接着上节讲到抽象工厂模式,接下来讲建造者模式。 水果店随着业务发展,想要举行各类促销活动来提升人气。 会员购买橘子+苹果+香蕉(可能还有别的水果)立减15元假日促销则橘子+苹果+香蕉(可能还有别的水果)立减10元 问题: 1.套餐组合包含各类水果的组合,较复杂(后续套餐越来越复杂)。2.套餐的创建步骤基本确定,设置商品价格/设置折扣/得到结算价。3.会不断的推出新的套餐。针对需求,我们需要考虑扩展性,针对上面三个问题,我们考虑用建造者模式来设计。 1.创建步骤 针对这几个步骤我们抽象出一个建造接口即builder接口用来创建套餐。套餐是一个对象我们定义一个类封装好属性,主要是各个水果的价格,折扣价,还有套餐总价: 结构图: 代码如下: //创建一个水果套餐Meal类public class FruitMeal { private Apple apple;//苹果--属性包含价格 private Banana banana;//香蕉价格 private Orange orange; //桔子价格 private int discount;//折扣价 private int totalPrice;//套餐总价 public void setDiscount(int discount) { this.discount = discount; } public void setApple(Apple apple) { this.apple = apple; } public void setBanana(Banana banana) { this.banana = banana; } public void setOrange(Orange orange) { this.orange = orange; } public int cost(){ return this.totalPrice; } public void init() { if (null != apple){ totalPrice += apple.price(); } if (null != orange){ totalPrice += orange.price(); } if (null != banana){ totalPrice += banana.price(); } if (totalPrice > 0){ totalPrice -= discount; } } public void showItems() { System.out.println("totalPrice:" + totalPrice); }}builder接口: ...

May 18, 2019 · 3 min · jiezi

工厂模式总结

四种工厂模式: 原则:1.解耦:把对象的创建和使用的过程分开。2.工厂负责对象的创建,包括其init方法的调用,黑盒创建工厂。(外界不需要知道过程)3.面向接口编程:使用者只管使用,只知其接口而不知实现类。 对比4.静态工厂:把所有对象创建逻辑都集中到一个类。5.工厂方法模式:一个工厂负责一个产品类的创建。6.抽象工厂模式:将一个系列的产品的工厂(即这些系列的产品有关联)合并成一个工厂,负责生产这个系列的产品。7.对象创建比较复杂时,按步骤一块块创建,让创建过程模板化。

May 18, 2019 · 1 min · jiezi

单例模式

单例模式具有的特性一个类仅能够实例化一次。单例模式的实现方式 使用枚举实现单例模式: public enum SingletonMode { INSTANCE; Resource resoure; SingletonMode() { this.resoure = new Resource(); } public Resource getResoure() { return resoure; }}

May 15, 2019 · 1 min · jiezi

观察者模式与发布订阅模式

观察者模式与发布/订阅模式观察者模式概念一个被观察者的对象,通过注册的方式维护一组观察者对象。当被观察者发生变化,就会产生一个通知,通过广播的方式发送出去,最后调用每个观察者的更新方法。当观察者不再需要接受被观察者的通知时,被观察者可以将该观察者从所维护的组中删除。 实现这个实现包含以下组件: 被观察者:维护一组观察者, 提供用于增加和移除观察者的方法观察者:提供一个更新接口,用于当被观察者状态变化时,得到通知具体的被观察者:状态变化时广播通知给观察者,保持具体的观察者的信息具体的观察者:保持一个指向具体被观察者的引用,实现一个更新接口,用于观察,以便保证自身状态总是和被观察者状态一致的首先,对被观察者维护的一组观察者(列表)进行建模 function ObserverList() { this.observerList = []}ObserverList.prototype.add = function(obj) { return this.observerList.push(obj)}ObserverList.prototype.Empty = function() { this.observerList = []}ObserverList.prototype.removeAt = function(index) { this.observerList.splice(index, 1)}ObserverList.prototype.count = function() { return this.observerList.length}ObserverList.prototype.get = function(index) { if (index > -1 && index < this.observerList.length) { return this.observerList[index] }}// Extend an object with an extensionfunction extend(extension, obj) { for (var key in extension) { obj[key] = extension[key] }}接着,对被观察者以及其增加、删除、通知能力进行建模 function Subject() { this.observers = new ObserverList()}Subject.prototype.addObserver = function(observer) { this.observers.add(observer)}Subject.prototype.removeObserver = function(observer) { this.observers.removeAt(this.observers.IndexOf(observer, 0))}Subject.prototype.notify = function(context) { var observerCount = this.observers.count() for (var i = 0; i < observerCount; i++) { this.observers.get(i).update(context) }}接着,对观察者进行建模,这里的 update 函数之后会被具体的行为覆盖 ...

May 15, 2019 · 3 min · jiezi

结构型模式代理模式

文章首发:结构型模式:代理模式 七大结构型模式之七:代理模式。简介姓名 :代理模式 英文名 :Proxy Pattern 价值观 :为生活加点料 个人介绍 :Provide a surrogate or placeholder for another object to control access to it.为其他对象提供一种代理以控制对这个对象的访问。(来自《设计模式之禅》) 你要的故事咱们从事 IT 行业,随时都可能上网查东西,如果网络速度慢或者网络访问受限制,那是相当的折磨,忍无可忍。而咱在国内网络比较特殊,有个墙围着,俗称防火长城。今天讲到代理模式,就来讲讲这道墙。这墙是这么实现的,我们上网,正常的网络是世界各地的网站我们都能访问,而加上这道墙,相当于在我们上网的时候做了一层代理,这一层代理把禁用的网站给过滤掉,使得我们没法访问被禁用的网站。下面通过代码来讲解。 定义一个互联网接口,里面有一个访问网站的通用方法 access。 /** * 互联网 */interface Internet { String access(String domain);}定义世界范围内的网络类,可以访问任何存在的网站。 /** * 世界网络 */class WorldNetwork implements Internet { @Override public String access(String domain) { System.out.println("访问网站:" + domain); return domain + "网站内容"; }}定义中国的网络类,就是代理类,实现墙的功能。disable 对象存储了在国内禁止访问的网站,用户在访问网站时(也就是调用 access 访问)先判断网站是不是在禁用的网站集合里面,如果是则禁用,如果不是则继续访问。 /** * 中国网络(就是代理) */class ChinnessNetwork implements Internet { private Set<String> disable; private Internet internet; public ChinnessNetwork(Internet internet) { this.internet = internet; this.disable = new HashSet<>(); this.disable.add("www.google.com"); this.disable.add("www.facebook.com"); } @Override public String access(String domain) { if (disable.contains(domain)) { System.out.println("禁止访问该网站:" + domain); return "禁止访问该网站:" + domain; } return internet.access(domain); }}测试代码,ChinnessNetwork 作为代理类,WorldNetwork 作为被代理类。 ...

May 15, 2019 · 1 min · jiezi

设计模式笔记整理

模式定义连接策略模式定义了算法簇,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户https://segmentfault.com/a/11...订阅模式(观察者模式)定义对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。https://segmentfault.com/a/11...装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰着提供了比继承更有弹性的替代方案。https://segmentfault.com/a/11...工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。https://segmentfault.com/a/11...抽象工厂模式抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不许奥明确指定具体类。https://segmentfault.com/a/11...单件模式确保一个类只有一个实例,并提供一个全局的访问点。https://segmentfault.com/a/11...命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其它对象,命令模式也支持可撤销的操作。https://segmentfault.com/a/11...适配器与外观模式将一个类的接口,转换成客户期待的另一个接口。适配器让原本接口不兼容的类可以合作无间。https://segmentfault.com/a/11...模板模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。https://segmentfault.com/a/11...

May 13, 2019 · 1 min · jiezi

设计模式模板模式

Head First设计模式》笔记整理...欢迎交流... 定义在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法结构保持不变,同事由子类提供部分实现。 OO原则封装变化多用组合,少用继承针对接口编程,不针对实现编程为对象之间的松耦合设计而努力类应该Udine扩展开放对修改关闭依赖抽象,不要依赖具体类只和朋友交谈别找我,我会找你实例先来看看咖啡和茶的冲泡 我们把prepareRecipe()抽象化 public abstract class CaffeineBeverage { //咖啡因饮料是一个抽象的类 final void prepareRecipe() { //prepareRecipe()被声明为final,因为我们不希望子类覆盖这个方法。我们将步骤2和4泛化成brew()和addCondiments() boildWater(); brew(); pourInCup(); addCondiments(); } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.printIn("Boiling water"); } void pourInCup() { System.out.printIn("Pouring into cup"); }}public class Tea extends CaffeineBeverage { public void brew() { System.out.printIn("Steeping the tea"); } public void addCondiments() { System.out.printIn("add Lemon"); }}public class Coffee extends CaffeineBeverage { public void brew() { System.out.printIn("Dripping Cooffee through filter"); } public void addCondiments() { System.out.printIn("Adding Sugar and Milk"); }}类图 ...

May 13, 2019 · 1 min · jiezi

设计模式单例模式

单例模式是 确保一个类只有一个实例,自行实例化并向系统提供这个实例 一个类只有一个实例对象,避免了重复实例的频繁创建和销毁降低了资源消耗并且共用一个对象有利于数据同步,例如WINDOWS的任务管理器、回收站、网站的计数器、线程池对象、配置文件的读取对象等两种创建方式:1.饿汉单例模式(最常用): 单例实例在类装载时就构建,急切初始化。(预先加载法)特点:线程安全、在类加载的同时已经创建好一个静态对象,调用时反应速度快,有可能从没用到,有一点点的资源浪费 //饿汉单例模式Demopublic class SingletonTest { //1.私有化该类的构造方法(不让别人new,只能自己new) private SingletonTest() { } //2.自己内部new一个对象 public static SingletonTest instance = new SingletonTest(); //3.给一个get方法,让外界取它 public SingletonTest getInstance() { return instance; }}2.懒汉单例模式: 单例实例在第一次被使用时构建,延迟初始化。 //懒汉单例模式Demopublic class SingletonTest2 { //1.私有化该类的构造方法(不让别人new,只能自己new) private SingletonTest2() { } //2.自己内部维护一个null对象(只要被调用一次就不再是了) public static SingletonTest2 instance = null; //3.给一个get方法,让外界取它,只有有人用才会new一个对象出来 public SingletonTest2 getInstance() { if (instance == null) { //TODO 多线程下可能会出现重复new的情况 instance = new SingletonTest2(); } return instance; }}总结: 两种模式各有所长 一种是时间换空间 一种是空间换时间 根据具体场景使用 ...

May 12, 2019 · 1 min · jiezi

装饰器代理模式与Spring-AOP

引言翻开to-do,注解认证中答应大家要讲解代理模式。 正好遇到了一道这样的题:抛开Spring来说,如何自己实现Spring AOP? 就喜欢这样的题,能把那些天天写增删改查从来不思考的人给PK下去,今天就和大家一切学习代理模式与Spring AOP。 代理与装饰器场景描述代理,即替代之意,可替代所有功能,即和原类实现相同的规范。 代理模式和装饰器模式很像,之前的装饰器讲的不是很好,这里换个例子再讲一遍。 宁静的午后,来到咖啡馆,想喝一杯咖啡。基础实现给你一个咖啡接口: public interface Coffee { /** * 打印当前咖啡的原材料,即咖啡里有什么 */ void printMaterial();}一个默认的苦咖啡的实现: public class BitterCoffee implements Coffee { @Override public void printMaterial() { System.out.println("咖啡"); }}默认的点餐逻辑: public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee.printMaterial(); }}点一杯咖啡。 装饰器模式优雅的服务生把咖啡端了上来,抿了一口,有些苦。 想加点糖,对服务生说:“您好,请为我的咖啡加些糖”。 /** * 糖装饰器,用来给咖啡加糖 */public class SugarDecorator implements Coffee { /** * 持有的咖啡对象 */ private final Coffee coffee; public SugarDecorator(Coffee coffee) { this.coffee = coffee; } @Override public void printMaterial() { System.out.println("糖"); this.coffee.printMaterial(); }}然后服务生就拿走了我的咖啡,去使用SugarDecorator为咖啡加糖,最后把加好糖的咖啡给我。 ...

May 11, 2019 · 2 min · jiezi

适配器与外观模式

《Head First设计模式》笔记整理...欢迎交流... 使用场景假设已有一个软件系统,你希望它能和一个新的厂商的类库搭配使用,但是这个心厂商设计出来的接口,不同于旧厂商的接口。 解决这个问题,我们就可以使用适配器 定义将一个类的接口,转换成客户期待的另一个接口。适配器让原本接口不兼容的类可以合作无间。类图 外观模式定义提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。这里注意一下外观模式与适配器模式的区别:外观模式定义的接口访问的是子系统的一群接口 “最少知识”原则也叫墨忒耳法原则。只和你的密友谈话方针 就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围内的方法: 该对象本身被当做方法参数而传递进来的对象此方法所创建或实例化的任何对象对象的任何组件//没有采用这个原则public float getTemp() { Thermometer thermometer = station.getTermometer(); //这里,我们从气象站取得温度计对象,再从温度计对象取得温度 return thermometer.getTemperature();}//采用这个原则public float getTemo() { return station.getTemperature();}以一个汽车类示例: public class Car { Engine engine; //这是类的一个组件,我们可以调用它的方法 //其它实例变量 public Car() {} public void start(Key key) { Doors doors = new Doors(); //这里创建了一个新对象,它的方法可以被调用 boolean authorized = key.turns(); //被当做参数传递进来的对子那个,其方法可以被调用 if(authorized) { engine.start(); //可以调用对象组件的方法 updateDashboardDisplay(); //可以调用对象内的本地方法 doors.lock(); //可以地阿偶创建或实例化的对象的方法 } }}“最少知识”原则和外观模式的关系

May 10, 2019 · 1 min · jiezi

设计模式命令模式

《Head First设计模式》笔记整理...欢迎交流... 定义将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其它对象,命令模式也支持可撤销的操作。![图片上传中...] 类图 代码演示public interface Command { public void execute();} public class LightOnCommand implement Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); //execute方法调用接收对象的on方法 }}//invoker请求者public class SimpleRemoteControl { Command slot; public SimpleRemoteControl(); public void setCommand(Command slot) { this.slot = slot; } public void buttonWasPressed() { slot.execute(); }}测试 SimpleRemoteControl remote = new SimpleRemoteControl();Light xx = new Light();LightOnCommand lightOn = new LightOnCommand(buttonWasPressed);remote.setCommand(lightOn);remote.buttonWasPressed();

May 9, 2019 · 1 min · jiezi

一步步理解观察者模式

今天说说观察者模式,这是一个非常常见的模式,很多事件的分发都基于此模式。 这里将从一个题目来说说此模式的使用,有这样一道题: 某市一家报社开张,市民订阅报纸,尝试使用观察者模式解决此问题。 在使用观察者模式前,先说说普通的处理方法。 class Publisher { public $title = ''; public function __construct($title) { $this->title = $title; } public function push() { echo "【{$this->title}】发布新闻" . PHP_EOL; }}class People { public $name = ''; public function __construct($name) { $this->name = $name; } public function pull(Publisher $publisher) { echo "【{$this->name}】接收到【{$publisher->title}】发布的新闻" . PHP_EOL; }}/* 初始化用户 */$peoples = [];for ($i = 1; $i <= 5; $i++) { $peoples[] = new People("用户{$i}");}/* 报社使用 push 方法发布新闻 */$publisher = new Publisher("报社1");$publisher->push();/* 所有订阅的市民使用 pull 方法接收报社对象 */foreach ($peoples as $people) { $people->pull($publisher);}每次 publishier 使用 push 方法,都需要循环一遍 $peoples 进行通知,当然这里可以通过一个函数封装起来,每次调用即可。 ...

May 8, 2019 · 2 min · jiezi

被误读的设计模式

目录概要 <!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --> 设计模式的开山之作对设计模式的误解关于使用设计模式的3个问题无处不在的设计模式如何解释设计模式<!-- /TOC --> 设计模式的开山之作1994年10月21日,有四个哥们儿出版了一本书,名字叫做《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)。 这四个哥们儿后来以“四人帮”(Gang of Four,GoF)著称,而他们的《设计模式:可复用面向对象软件的基础》一书也就成为了设计模式的开山之作。 对设计模式的误解之前发布了几篇介绍设计模式的的博客,看到有的读者对设计模式的评论是: 看了也不会,会了也不用设计模式有使用场景的,不能生搬硬套我写了那么多年代码,从来没用过设计模式关于使用设计模式的3个问题这里我不得不说,大家对设计模式是有很多误解的,面对设计模式我们至少要思考下面3个问题: 首先,什么是设计模式?设计模式简而言之就是一些常见软件设计问题的标准解决方案。 正如设计模式的开山之作《设计模式:可复用面向对象软件的基础》一书中所说的那样: 所有结构良好的面向对象体系结构中都包含了许多设计模式。设计面对向软件比较困难,而设计可复用的面向对象软件就更加困难。内行的设计者知道:不是解决任何问题都要从头做起。他们更愿意复用以前使用过的解决方案。当找到一个好的解决方案,他们会一遍又一遍地使用。这些经验是他们成为内行的部分原因。可以说,如果问题是我们的敌人,代码是我们的剑,设计模式就是高手心中的剑谱。 接着,怎么使用设计模式《Head First 设计模式》一书的作者里曼说,使用设计模式有3个层次: • Beginner —— 初级选手,在编程的时候无处不用设计模式,认为用的模式越多设计就越好。 • Intermediate —— 中级选手,在编程的时候知道何时该用什么设计模式,而什么时候不该用。 • Zen —— 到了禅的境界,“他山之石,可以攻玉”。设计模式被用来简化设计,让设计更优雅。 非要把设计模式硬塞到设计之中,那只是初级菜鸟的层次。 最后,设计模式在实际生产中使用的多不多?设计模式本身就是源自实际问题的优秀解决方案的总结,因此在很多基础的架构和框架里,都可以看到设计模式的影子。 比如Java开发者经常使用的Spring框架,从创建型的工厂模式、单例模式、原型模式,再到结构型的享元模式、代理模式,再到行为型的观察者模式、模板模式,在其源码中随处可见。 无处不在的设计模式设计模式不是空中楼阁,也不是只有面试和吹牛的时候才能放在嘴边的炫耀资本,而是一个优秀的开发者可以让自己的设计更加优雅的不可或缺的好帮手。 并且设计模式也并不是软件行业特有的现象,很多行业中有经验的从业者都会使用各种“模式”来优化自己的设计。 就拿剧本创作举例,如果一个剧作家要写一个剧本,并不是完全凭空创建的,也有各种业内普遍应用的模式,例如“悲剧英雄模式”(《麦克白》、《哈姆雷特》)、“凄美爱情模式”(《罗密欧与朱丽叶》、《梁山伯与祝英台》)等等,都是剧作家可以使用的“设计模式”。 如何解释设计模式一般而言,要介绍一个设计模式,至少要包含如下4个要素: 问题(Problem)描述了我们遇到了哪些问题,也就是某个设计模式的使用场景。 解决方案(Solution)为了解决上面遇到的问题,我们想到了哪些解决方案。其中最具有普遍性的方案往往就是我们的设计模式的内容。 效果(Consequence)设计模式就像一个模板,可以为解决不同的问题提供思路。而解决问题的效果就是衡量一个设计模式在某个场景下是否适合的关键因素,一般我们要衡量的方面有灵活性、可移植性、可扩充性、性能等。 模式名称(Pattern Name)一个好的名字,可以让我们记住某个模式,并且可以望名知意,使其更便于传播。甚至作为我们思维方式的一部分保留下来。 以上面4个要素为基础,我们解释设计模式可以分为如下几个方面: 模式名称:模式的名字模式别名:模式的其他名字或者昵称模式分类:模式属于那种类型模式意图:回答模式是干什么的,为了解决什么问题等问题模式结构:模式包含哪些角色,以及这些角色之间的关系模式适用性:哪些场景适合使用该模式模式实现:通过例子来展示模式的实现过程已知应用:该模式已经被使用在了什么地方相关模式:模式中用到的模式,与该模式有替代关系的模式发布/订阅模式 和 事件驱动模型 从原理到实战的系列文章: 设计模式之发布订阅模式(1) 一文搞懂发布订阅模式设计模式之发布订阅模式(2) Redis 发布/订阅模式设计模式之发布订阅模式(3) 深入Spring Events事件驱动模型设计模式之发布订阅模式(4) Guava Eventbus 事件处理设计模式之发布订阅模式(5) Spring Events源码解析 ...

May 7, 2019 · 1 min · jiezi

javascript设计模式与开发实践全书深度解析一之前言

相信很多人都看过《javascript设计模式与开发实践》这本书,每个人都有自己的体会感受,作为前端3年开发经验的程序猿,用我自认为还可以的实践经验来与大家谈谈这本书。这章是前言,后面陆续会讲解每个设计模式。那么我写这篇文章的意义是: 对书本感触颇深(监督自己把书看完)与大家共同交流设计模式(炫耀一下自己的技术)利用公司项目空闲时期多学点知识(划水)好了,废话不多说,先讲一下什么是设计模式以及设计模式有什么用。 什么是设计模式书中说:在面向对象软件设计过程中针对特定的问题的简洁而优雅的解决方案。注意几个关键词: 面向对象 咦,那我们javascript并不是严格的面向对象语言啊。其实吧,说javascript面向对象是没有问题的,它拥有面向对象的很多特性,可以有构造函数,可以做继承,es6里面还引入类的概念,甚至也有私有变量,symbol等,那么很自然的它可以说是面向对象。针对特定的问题 啥是特定问题呢?那我说说啥不是特定问题吧,比如你要打印一个“我很帅”,用的到设计模式吗?很复杂吗?答案是否定的,这个就不是特定问题。那其实特定问题是用常规的做法,流水账式编程会让代码变得很不好的一个场景下产生的问题。不多说了...简洁而优雅 这个也是字面意思吧,如果一个设计模式让代码变得很复杂,很难维护,或者性能很差,代码很丑陋,那么我想没人会去用吧。so...设计模式就是“方法”!重在设计,模式只是名词,给设计套上一个名称,那么你平时旅游,先去哪里,到了之后先去宾馆还是先玩,要不要找个妹子一起去,一系列的都可以叫做设计。 设计模式有什么用解释什么是设计模式的时候其实也讲了它的用处,重点是让你写代码更舒服,爽,省时省力,代码更优雅,emoji...书中提到了16种设计模式,有16种让你爽的办法,还不想尝试下?

May 7, 2019 · 1 min · jiezi

简单说说访问者模式的由来

在所有设计模式中,访问者模式算得上比较难理解的一种设计模式。虽然这种模式比较难理解,但是也需要去知道这种模式具体是怎么回事儿,我将从最简单的代码讲起,尝试去说说这种模式的由来。 先给定一个场景:顾客去商店买物品,购物结束后需要在收银员进行结账。 这里具体罗列一下几个关键词:顾客(client)、物品(goods)和收银员(cashier) 于是就有以下代码: class Cashier { public function checkGoods(Goods $goods) { echo "【Goods】Title: {$goods->title}, price: {$goods->price}"; }}class Goods { public $title = ''; public $price = 0; public function __construct($title, $price) { $this->title = $title; $this->price = $price; }}/* client */$pencil = new Goods('铅笔', 2.5);$cashier = new Cashier();$cashier->checkGoods($pencil);总体解释: 顾客购买一支铅笔顾客将铅笔交给收银员结账(checkGoods)以上没问题,很多类似的项目也是这样设计的。宏观来说 cashier 作为服务去结账是没有问题的,毕竟收银员就是给客户结账的,其实这里有点类似外观模式(Facade Pattern)。 那么有没有办法换一种思路去设计呢?看下面的代码: class Cashier { public function checkGoods(Goods $goods) { echo "【Goods】Title: {$goods->title}, price: {$goods->price}"; }}class Goods { public $title = ''; public $price = 0; public function __construct($title, $price) { $this->title = $title; $this->price = $price; } public function checkSelf(Cashier $cashier) { $cashier->checkGoods($this); }}$pencil = new Goods('铅笔', 2.5);$cashier = new Cashier();$pencil->checkSelf($cashier);以上代码在 Goods 中加了一个 checkSelf 方法用于接收 Cashier 实例,在 checkSelf 的这个方法中,我们调用了实例的 checkGoods 方法。改写 client 端的代码,将不在使用 checkGoods 方法进行检查,而使用的是 checkSelf 方法将 cashier 传入 Goods 中。 ...

May 7, 2019 · 1 min · jiezi

结构型模式享元模式

文章首发:结构型模式:享元模式 七大结构型模式之六:享元模式。简介姓名 :享元模式 英文名 :Flyweight Pattern 价值观 :共享富贵 个人介绍 :Use sharing to support large numbers of fine-grained objects efficiently.使用共享对象可有效地支持大量的细粒度的对象。(来自《设计模式之禅》) 你要的故事还记得笔袋么?可能有人已经忘记了,在写这篇文章之前其实我也忘了,从初中开始就再也没用过笔袋。拿笔袋来讲享元模式再适合不过了。笔袋放各种各样的笔,今天我们不讲别的,就讲蜡笔。前段时间在逛公园的时候,看到一位老师在画画,画的就是蜡笔画,第一次看到真正的蜡笔画,真的很震撼,原来蜡笔也可以把景色画得那么美。当时偷偷拍了一张,看下图。 我们就拿这幅画来说,里面画了草、树、路、山、天空等等。如果没有用享元模式,我们可能这样子实现。 蜡笔接口。 interface ICrayon { void draw(String place);}蜡笔。 /** * 蜡笔 */class Crayon implements ICrayon { private String color; public Crayon(String color) { System.out.println("---新建【" + color + "】蜡笔" ); this.color = color; } @Override public void draw(String place) { System.out.println("用" + this.color + "蜡笔画" + place); }}测试代码。这幅画是小明和小红一起画,小明画了草和路,小红画了树和蓝天。 ...

May 7, 2019 · 1 min · jiezi

设计模式工厂方法模式

《Head First设计模式》笔记整理...欢迎交流... 定义定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。OO原则封装变化多用组合少用继承针对接口变成,不针对实现编程为交互对象之间的松耦合设计而努力类应该对扩展开放,对修改关闭要依赖抽象,不要依赖具体类类图 正如在正式定义中所说的,常常听到其他开发人员说,工厂方法让子类决定要实例化的类是哪一个。希望不要理解错误,所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的具体产品是哪一个。选择使用哪个产品的子类,自然就决定看实际创建的产品是什么。 依赖倒置原则要依赖抽象,不要依赖具体类。这个原则听起来很像“针对接口编程,不针对实现编程”。然而,这里更强调“抽象”。这个原则说明了不能让高层组件依赖低层组件,“两者”都应该依赖抽象。 几个方法帮助你遵循此原则...... 变量不可以持有具体类的引用不要让类派生自具体类不要覆盖积累中已经实现的方法如果覆盖基类已经实现的方法,那么你的基类不是一个真正适合被继承的抽象。基类中已经实现的方法,应该由所有的子类共享。 需要注意的是,所有的原则都是为了更好的设计,并不是必须随时都遵循的。 创建一个pizza工厂public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; pizza = ceratePizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } public abstract Pizza createPizza(String type); //其它方法}//工厂方法是抽象的,所以必须依赖子类来处理对象的创建//工厂方法必须返回一个产品//工厂方法将客户,和实际创建具体产品的代码分隔开来//工厂方法可能需要传递参数(也可能不需要)来指定所需要的产品abstract Product factoryMethod(String type)public abstract class Pizza { String name; String dough; String sauce; ArrayList topppings = new ArrayList(); void prepare() { ... }; void brake() { ... }; void cut() { ... }; void box() { ... }; Public String getName() { ... };}

May 5, 2019 · 1 min · jiezi

结构型模式外观模式

文章首发:结构型模式:外观模式 七大结构型模式之五:外观模式。简介姓名 :外观模式 英文名 :Facade Pattern 价值观 :统一口径、一致对外 个人介绍 :Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。(来自《设计模式之禅》) 你要的故事作为开发同学,我们平时打交道最多的就是需求同学和测试同学,公司小的时候,什么事情都全靠吼,工作也直接一对一,一个需求下来,需求同学先跟开发同学一起跟进这个需求,需求开发完成了,需求同学和测试同学沟通了需求的测试要点,测试同学就开测。这个过程中需求一直跟到上线。我们用代码来描述这个过程。 开发同学,负责开发需求。 /** * 开发同学 */class Developer { public void develop(String name) { System.out.println("开发需求:" + name); }}测试同学,负责测试需求。 /** * 测试同学 */class Tester { public void test(String name) { System.out.println("测试需求:" + name); }}需求同学,负责提需求,也负责跟进需求的开发、测试,直到上线。 ...

May 5, 2019 · 1 min · jiezi