把模板方法应用到实际项目中

阅读原文:把「模板方法」应用到实际项目中 如果不在工作中思考,模板方法模式的概念和定义即使背再多遍估计也是徒劳,今天我思考如何解决实际项目中的问题时发现,模板方法正是解决问题的良策。 <!--more--> 需求我们项目中要实现一个创建会议的需求,但我们系统可设置将会议同步到其他第三方系统中。而在创建会议前要经过几个必须的步骤,比如验证冲突,计算循环规则,对比时间段是否符合等。 思路不同点创建会议的方式不同: 本地系统创建第三方系统创建相同点验证会议是否冲突根据循环规则计算出预定时间段是否符合预定规则特殊点即使会议又冲突,但有需求是依然要能创建成功!所以验证冲突步骤是可选的。 业务梳理后如下: 开始抽象只有抽象类能实现我们的要求,既能要一部分方法有实现又能实现抽象方法不用实现。 @Slf4jpublic abstract class AbstarctRecurringHandler { /** * 处理创建会议 */ public final void handle() { if (isConfirm()) { calculateConflict(); } calcBookingPeriod(); checkRule(); createEvent(); } /** * 验证规则 */ public void checkRule() {} /** * 获取预定的所有会议时间段 * * @return */ public List<Period> calcBookingPeriod() {} /** * 计算冲突会议 */ public void calculateConflict() {} /** * 钩子方法 * * @return */ public abstract boolean isConfirm(); public abstract void createEvent();} 模板方法我们这里的handle方法已经固定了整个创建会议的流程,并且我们使用final修饰,表示不允许别人修改这个过程。不同的创建可能有不同的人来完成,这个能够避免部分人员的粗心大意也就是规范了创建流程,而且其他开发人员也不必再关心除创建会议之外的其他过程。 ...

April 30, 2019 · 1 min · jiezi

JavaScript基础学习面向对象对象创建之工厂模式

前言上一章回顾了JS对象的属性类型,那么除了我们常用的new Object()构造函数创建对象和字面量方式创建对象的方式外,还需要用到更多的模式来解决对象被多次复用的问题。什么意思呢?就是我们很有可能会在各个地方去使用已经创建过的对象,但是对象里的属性值有可能是不同的。举个例子: let obj = { name:"勾鑫宇", age:18, myName:function(){ console.log(this.name) }}如果我们想继续使用这个obj对象,但里面的属性值需要改变,我们可能会想到这样做: let obj2 = obj;obj2.name = "张三";但是这样做有一个问题,由于JS中引用类型的机制,当你修改obj2的同时,obj也被改变了。所以我们就不得不像下面这样再重新创建一个obj2对象。 let obj2 = { name:"张三", age:23, myName:function(){ console.log(this.name) }}这样就无形中给我们增加了很多工作,代码量也会大大增加,而这么多重复的东西是完全没必要的。于是我们就需要用到创建对象的各种设计模式来解决这个问题了,这章先讲工厂模式。 工厂模式根据书上和各种百科的解释,还是先来一个官方版本,然后写写我的理解吧。 官方解释:工厂是构造方法的抽象,抽象了创建具体对象的过程。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。 工厂模式分为三类:简单工厂模式、工厂方法模式和抽象工厂模式 我的理解:至于为什么要叫工厂模式,就是因为这个模式将我们所需要的逻辑代码给封装到了一个函数里面,然后我们只需要传入相应的参数,就能够去获取需要的结果。这个过程就如同我们向工厂要东西一样简单。比如我们需要一台电脑,只需要告诉工厂电脑的屏幕尺寸是多大、系统是Win还是Linux、内存是多少G,而不用关心屏幕是怎么制作的,系统是怎么设置的,内存条是怎么做的,最重要的是最终这个电脑是怎么组装出来的我们也不关心,只关心最后能拿到一台我们所需的成品电脑就行了。 由于《JS高编》里这一部分只讲了简单工厂模式的实现,其他两种模式就先不说,更多的可以去看《JS设计模式》。 //创建一个简单函数,就当作一个类//第一种方式是通过new Object()创建对象,最后返回它function person(name,age){ let o = new Object(); o.name = name; o.age = age; o.myName = function(){ console.log("我的名字是"+o.name) } return o;}let person1 = person("勾鑫宇",18);let person2 = person("张三",23);person1.myName();//输出“我的名字是勾鑫宇”person2.age;//输出23//第二种方式是通过字面量形式创建对象function person(name,age){ let o = { name: name; age: age; myName: function(){ console.log("我的名字是"+o.name) } } return o;}上面的方法使用简单工厂模式封装了一个类,然后我们只需要传入名字和年龄的参数就行了。那么我们还可以添加稍微复杂一点的逻辑在这个工厂里面。 ...

April 29, 2019 · 1 min · jiezi

结构型模式装饰模式

文章首发:结构型模式:装饰模式 七大结构型模式之四:装饰模式。简介姓名 :装饰模式 英文名 :Decorator Pattern 价值观 :人靠衣装,类靠装饰 个人介绍 :Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。(来自《设计模式之禅》) 你要的故事夏天到了,吃货们期待的各种各样冷冻零食就要大面积面向市场,什么冰淇淋、雪糕、冰棍等等。今天的装饰模式不讲这些都受欢迎的零食,讲讲那乌黑滴龟苓膏。不知道大伙们喜不喜欢吃龟苓膏,我是挺喜欢的,不喜欢的人很多都闲它苦,应该没有人愿意在没加任何糖类的情况下吃龟苓膏。很多糖水店会提供几种龟苓膏,比如蜂蜜龟苓膏、牛奶龟苓膏。下面我们空想出一个场景来。 天气到了 30℃,小明和小红加班到了 10 点,一起下班,路过一家糖水店,小红萌生了吃糖水解解热的想法,小明最近涨薪,就提出要请小红吃糖水,他们进去糖水店,小红想着好久没吃龟苓膏了,就想吃吃,怀念一下童年那段在农村夜晚吃龟苓膏的时光。糖水店里面有 3 种龟苓膏,一种是普通龟苓膏(这老板居然提供不加任何糖分的,过分了),一种是蜂蜜龟苓膏,另外一种是牛奶龟苓膏,小明点了一份蜂蜜龟苓膏,小红想加蜂蜜和牛奶,就咨询了老板娘,能否同时加蜂蜜和牛奶,老板娘用那东北腔爽快地回复小红:行。小明和小红就等龟苓膏上桌。。。脑洞到这。 我们来把这个故事套入到装饰模式里去。上面故事里老板卖 3 种龟苓膏,而都不满足小红的需求,小红想要的是蜂蜜牛奶龟苓膏,如果用继承来实现龟苓膏,那就无法满足小红的要求了,因为继承直接固定了龟苓膏的做法,加什么就是什么,要蜂蜜牛奶龟苓膏,那就需要另外一个龟苓膏类代表蜂蜜牛奶龟苓膏;而用装饰模式则不同,下面看看装饰模式的实现代码。 龟苓膏抽象类,该类定义了制作龟苓膏的抽象方法。 /** * 龟苓膏 */abstract class HerbalJelly { /** * 制作龟苓膏方法 */ public abstract void process();}老板提供的最基本的龟苓膏,这种龟苓膏不加任何料,就是那苦苦的龟苓膏,我们称它为普通龟苓膏。 /** * 普通龟苓膏 */class CommonHerbalJelly extends HerbalJelly { @Override public void process() { System.out.println("盛一碗龟苓膏"); } }另外 2 种龟苓膏:蜂蜜龟苓膏和牛奶龟苓膏,不是用继承实现,而是用装饰器实现,我们可以发现这 2 种龟苓膏都是基于上面普通龟苓膏添加不同的糖类食品制作而成。下面实现一个抽象类充当装饰器。 ...

April 29, 2019 · 1 min · jiezi

设计模式装饰着模式

定义动态地将责任附加到对象上,若要扩展功能,装饰着提供了比继承更有弹性的替代方案。OO原则封装变化多用组合少用继承针对接口编程,不针对实现编程为了交互对象之间的松耦合而努力类应该对扩展开放,对修改关闭开闭原则我们的目标是使类容易扩展,在不改变现有代码的情况下,就可以搭配新的行为 认识装饰者模式以星巴此咖啡为例。比如,客户想要摩卡和奶泡深赔咖啡。那么,要做的是: 那一个深赔咖啡(DarkRoast)对象以摩卡(Mocha)对象装饰它以奶泡(Whip)对象装饰它调用cost()方法,并依赖委托将调料的价格加上去 装饰者和被装饰者对象有相同的超类型你可以用一个或多个装饰者包装一个对象既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象替代它装饰者可以在所委托被装饰者的行为之前与或之后,加上自己的行为,以达到特定的目的对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象类图

April 28, 2019 · 1 min · jiezi

php管道模式手测

先写在这里,待完善 interface PipelineInterface{ public function send($traveler); public function through($stops); public function via($method); public function process();}interface StageInterface{ public function handle($payload);}class StageOne implements StageInterface{ public function handle($payload) { echo $payload . ' He is a '; }} class StageTwo implements StageInterface{ public function handle($payload) { echo 'awesomeman'; }} class Pipe implements PipelineInterface{ protected $container; protected $passable; protected $pipes = []; protected $via = 'handle'; public function __construct($container) { $this->container = $container; } public function send($passable) { $this->passable = $passable; return $this; } public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; } public function via($method) { $this->via = $method; return $this; } public function process() { foreach ($this->pipes as $pipe) { call_user_func([$pipe, $this->via], $this->passable); } }}$container = 'a';$payload = 'wa';$pipe = new Pipe($container);$pipe->send($payload)->through([(new StageOne), (new StageTwo)])->process();

April 28, 2019 · 1 min · jiezi

设计模式订阅模式观察者模式

《Head First设计模式》笔记整理...欢迎交流... 定义定义对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。OO原则封装变化多用组合,少用继承针对接口编程,而不是针对实现编程为了交互对象之间的松耦合而努力一对多关系 定义类图 松耦合的威力松耦合:当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者模式让主题和对象之间松耦合。 为什么呢? 关于观察者的一切,主题只知道观察者实现看某个接口(即Observer接口)。主题不需要知道观察值是谁,做了些什么或其他任何细节。 建立一个气象站已知由气象站提供WeatherData对象,用来追踪目前的天气状况(温度、湿度、气压)。我们需要建立三个布告板(目前状况、气象统计、天气预报),分别显示目前的状况。并且要求此应用是可以扩展的。 public interface Subject { public void registerObserver(Observer o); //传入一个观察者变量,用来注册观察者 public void registerObserver(Observer o); //传入一个观察者变量,用来删除观察者 public void notifyObserver(); //当主题状态改变时,通知观察者}public interface Observer { // 所有的观察者都必须实现update方法,以实现观察者接口 public void update(float temp, float humidity, float pressure); //当气象状态值改变时,主题会把这些状态值当做方法的参数,传递给观察者}public interface DisplayElement { public void display(); // DisplayElement接口只包含一个方法,那就是display。当不高者需要显示时,调用此接口}在weatherData中实现主题接口 public class WeatherData implements Subject { private ArrayList observers; private float temoerature; // 温度 private float humidity; // 湿度 private float pressure; // 气压 public void registerObserver(Observer o) { observers.add(o); } public void removeObserver(Observer o) { int i = observers.indexof(o); if(i >= 0) { observers.remove(o); } } public void notifyObserver(Observer o) { for(int i = 0; i < observers.size(); i++) { Observer observer = (Observer) observers.get(i); observers.update(temoerature, humidity, pressure); } } public void measurementsChanged() { notifyObservers(); // 当从气象站得到耿勋观测值时,我们通知观察者 } public void setMeasurements(float temoerature, float humidity, float pressure) { // 这是一个测试方法 this.temoerature = temoerature; this.humidity = humidity; this.pressure = pressure; this.measurementsChanged(); } //WeatherData的其它方法}下面建立布告板 ...

April 28, 2019 · 1 min · jiezi

漫谈代理模式

本文首发于泊浮目的专栏:https://segmentfault.com/blog...前言代理模式是在编程中非常常见的设计模式.笔者在面试的过程中也经常会问到相关的问题,但是很多同学答的并不尽人意.在这篇文章中,笔者想和大家聊聊代理模式的应用及一些实践. What先来一张图 我们可以很明显的看到,代理和客户端发生了耦合,而目标端则与客户端解耦. Why上文提到了一点,松耦合.而在任何设计模式中,他们的目的都在以下范围内: 减少代码冗余度,提高代码复用性松耦合这里提到了代码的复用性,也可以多嘴一句,代理模式可以帮助我们实现The Open Closed Principle. 在这里,我们可以举一个例子.Target可能是一位不错的程序员,client是一家公司.在整个招聘流程中,如果Proxy是猎头,有些猎头则可能会想办法帮程序员提高身价.而如果Proxy是Hr,则可能会来杀杀价.而程序员走的流程可能一直是一样的: 电面到面签合同我们可以把不同的行为(讨价还价的特殊技巧)写在不同的Proxy里(HrProxy or 猎头Proxy),而我们的程序员只要专心走流程就行了. How以Java中最常用的框架——Spring为例.Spring最主要提供了2个功能: IOC(Inversion of Control)AOP(Aspect Oriented Programming)而我们知道,Spring的AOP本质上是通过代理模式来做的.接下来我们来详细聊聊Spring提供的4种类型的AOP支持: 基于代理的经典Spring AOP;纯POJO切面;@AspectJ注解驱动的切面;注入式AspectJ切面(适用于Spring各版本)。前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。 而SpringAOP支持两种模式的动态代理,JDK Proxy和cglib.当Spring发现目标被代理类实现就接口时,则用JDK Proxy来实现. JDK Proxy不完全通过反射来做,也有ASM进行字节码操作的.本质是通过接口约定来做的cglib完全通过ASM字节码来做.本质通过继承的方式实现 代码大概长这样: //spring aop 生成的代理public class SpringAopTargetProxy extends Target{ public void operate(){ //spring aop method1... super.operate(); //spring aop method2... }}而AspectJ是通过编译时编织来做的,即在编译时插代码进去.所以可以认为它基于静态代理来做AOP. 基于以上,我们也可以推导出SpringAOP对于finalorstatic方法是无效的. call和execution有什么区别呢?call就是在调用这个方法的地方插入代码execution就是在调用这个方法的前面插入代码代理模式的变化形式之前,我们根据代理生成的时机来区分了静态代理和动态代理.而根据使用方式,常见则有两类: Virtual Proxy:只有当真正需要实例时,它才生成和初始化实例Remote Proxy:远程代理可以让我们不必关心RealSubject角色是否在网络上,而是像调本地方法一样调用它的方法.Java的RMI(Remote Method Invocation)就相当于远程代理.类似的设计模式AdapterAdapter模式适配了两种具有不同接口(API)的对象,以使它们可以一同工作。而在Proxy模式中, Proxy角色与RealSubject角色的接口(API )是相同的(透明性)。 DecoratorDecorator模式与Proxy模式在实现上很相似(比如API的一致性),不过它们的使用目的不同——Decorator模式的目的在于增加新的功能。而在Proxy模式中,与增加新功能相比,它更注重通过设置代理人的方式来减轻本人的工作负担.

April 28, 2019 · 1 min · jiezi

JS设计模式之Mixin混入模式

概念Mixin模式就是一些提供能够被一个或者一组子类简单继承功能的类,意在重用其功能。在面向对象的语言中,我们会通过接口继承的方式来实现功能的复用。但是在javascript中,我们没办法通过接口继承的方式,但是我们可以通过javascript特有的原型链属性,将功能引用复制到原型链上,达到功能的注入。 示例下面通过一个简单的例子来演示这个模式 var Car = function(settings) { this.model = settings.model || "no model provided" this.color = settings.color || "no color provided"}var Action = function() {}Action.prototype = { driveForward: function() { console.log("drive forward") }, driveBackward: function() { console.log("drive backward") }, driveSideways: function() { console.log("drive sideways") }}//混入模式的实现function Mixin(recClass, giveClass) { if(arguments.length > 2) { for(var i = 2, lenth = arguments.length; i < lenth ; ++ i) { var methodName = arguments[i] recClass.prototype[methodName] = giveClass.prototype[methodName] } }else { for(var methodName in giveClass.prototype) { if(!recClass.prototype[methodName]) { recClass.prototype[methodName] = giveClass.prototype[methodName] } } }}Mixin(Car, Action , "driveForward", "driveBackward")var myCar = new Car({ model: "BMW", color: "blue"})myCar.driveForward() //drive forwardmyCar.driveBackward() //drive backward//不指定特定方法名的时候,将后者所有的方法都添加到前者里Mixin(Car, Action)var mySportsCar = new Car({ model: "Porsche", color: "red"})mySportsCar.driveForward() //drive forward优缺点优点有助于减少系统中的重复功能及增加函数复用。当一个应用程序可能需要在各种对象实例中共享行为时,我们可以通过在Mixin中维持这种共享功能并专注于仅实现系统中真正不同的功能,来轻松避免任何重复。 ...

April 26, 2019 · 1 min · jiezi

设计模式策略模式

《Head First设计模式》笔记整理...欢迎交流... 定义定义了算法簇,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。这里稍微提醒一下,策略模式是我们学习的第一个模式,理解它还是非常重要的。这个模式跟后面讲到的 模板模式 有些相似的地方,我在学习的时候,看到模板模式已经忘记什么是策略模式了,看的一脸懵逼。不过没关系,后面我会针对这两个进行整理,这里要记住的是,策略模式封装了一组可以互换的算法簇,是针对接口编程的。 OO原则封装变化多用组合,少用继承针对接口编程,而不是针对实现编程封装变化为什么要封装变化?先来看下面的例子。 dock类是所有鸭子的超类,其中,飞行(fly)行为和呱呱叫(quack)行为会随着鸭子的不同而改变。如果我们不区分变化,将所有的行为定义在超类里。如图: 显而易见的,这种设计会导致 改变牵一发而动全身不利于复用,不利于维护代码在多个子类中重复很难知道鸭子的全部行为运行时代码不容易改变为了便于以后可以轻易地改动和扩展此部分,而不会影响不需要改变的其它部分,我们把变化的部分取出并封装起来。 使用继承如何?设计一个flyable接口和一个quackable接口。 哇,一堆重复的代码,而且代码难以复用。甚至在会飞的鸭子中,飞行的动作也可能是千变万化的。如果我们想要更加弹性一点,在运行时也可以动态地选择行为,该怎么办呢? 针对接口编程针对接口编程,而不是针对实现编程。这里所谓的“接口”有多个含义,接口可以是一个“概念”。”针对接口变成“,关键就在于“多态”,可以更明确地说,变量的声明类型应该是超类型。这也意味着,声明类是不必理会以后执行时的真正的对象类型。 如此,鸭子的行为被放在分开的类中,此类专门提供行为的接口实现。这样,鸭子类就不再需要知道行为的实现细节。 多用组合,少用继承当你讲两个类结合起来使用,就是组合。如图本列,将飞行行为和呱呱叫行为委托给FlyBehavior和QuackBehavior代为处理。组合建立系统有很大的弹性,不仅可将算法簇封装成类,更可以在运行时动态地改变行为,只要组合的行为对象符合正确的接口即可。 代码演示publick abstract class Duck { //为行为接口类型声明两个引用变量,所有鸭子子类都继承它们 FlyBehavior flyBehavior; QuackBehavior quackBehavior; public Duck() { } publick abstract void display(); public void performFly() { flyBehavior.fly(); //委托行为给类 } public void performQuack() { quackBehavior.quack(); //委托行为给类 } public viod setFlyBehavior(FlyBehavior fb) { flyBehavior = fb; //动态设定飞行行为 } public viod setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; //动态设定呱呱叫行为 }}-----------------------------------------------------------public interface FlyBehavior { public void fly();}public class FlyWithWings implements FlyBehavior { public viod fly() { System.out.printIn("I'm flying!"); }}public class FlyNoWay implements FlyBehavior { public void fly { System.out.printIn("I can't fly!"); }}-----------------------------------------------------------public class QuackBehavior { public void quack();}public class Quack implements QuackBehavior { public void quack() { //此处省略 }}public class MuteQuack implements QuackBehavior { public void quack() { //此处省略 }}public class Squack implements QuackBehavior { public void quack() { //此处省略 }}//制造一个新的鸭子类型:模型鸭public class ModelDuck extends Duck { public ModelDuck() { flyBehavior = new FlyNoWay(); quackBehavior = new Quack(); } public viod display () { System.out.printIn("I'm a model duck"); } }--------------------------------------------------------//新建一个新的FlyBehavior类型public class FlyRocketPowered implements FlyBehavior{ public viod fly() { System.out.printIn("I'm flying with a rocket!"); }}最后,来看看一个简单的测试 ...

April 25, 2019 · 1 min · jiezi

Java单例模式实现

单例模式可能是代码最少的模式了,但是少不一定意味着简单,想要用好、用对单例模式,还真得费一番脑筋。本文对Java中常见的单例模式写法做了一个总结,如有错漏之处,恳请读者指正。 饿汉法顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下: public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; }} 这样做的好处是编写简单,但是无法做到延迟创建对象。但是我们很多时候都希望对象可以尽可能地延迟加载,从而减小负载,所以就需要下面的懒汉法: 懒汉法单线程写法这种写法是最简单的,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。如果有两条线程同时调用getSingleton()方法,就有很大可能导致重复创建对象。 public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton() { if(singleton == null) singleton = new Singleton(); return singleton; }}考虑线程安全的写法这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用synchronized进行加锁。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。如此即可从语义上保证这种单例模式写法是线程安全的。注意,这里说的是语义上,实际使用中还是存在小坑的,会在后文写到。 public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } return singleton; } }兼顾线程安全和效率的写法虽然上面这种写法是可以正确运行的,但是其效率低下,还是无法实际应用。因为每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正遇到需要new的情况是非常少的。所以,就诞生了第三种写法: ...

April 25, 2019 · 1 min · jiezi

结构型模式组合模式

文章首发:结构型模式:组合模式 七大结构型模式之三:组合模式。简介姓名 :组合模式 英文名 :Composite Pattern 价值观 :专门解决各种树形疑难杂症 个人介绍 :Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。(来自《设计模式之禅》) 你要的故事今天咱们再讲讲咱们程序猿的组织架构。技术类的组织架构比较单一,基本上都是这样:经理--->组长--->工程师,如下图所示。 各个公司的 title 可能不太一样,但是基本是差不多这种架构,按职业发展,从入职到能独立开发需求便为工程师,从独立开发需求到能带小团队开发便为组长,从带小团队开发到能带几个团队一起协作开发便为经理。 假设目前有一家公司,技术部就 4 个人,大熊担任经理,中熊担任组长,小熊1和小熊2担任工程师。下面的代码都围绕这个假设编写。 非组合模式我们先来一个非正常的实现方案:从组织架构里,有 3 个角色,分别是经理、组长、工程师,那么我们就按角色去实现一番。 Manager 为经理类,经理下有多个组长 leaders。 /** * 经理 */class Manager { private String name; private List<Leader> leaders; public Manager(String name) { this.name = name; this.leaders = new LinkedList<>(); } public void add(Leader leader) { this.leaders.add(leader); } public void remove(Leader leader) { this.leaders.remove(leader); } public void display(int index) { for (int i = 0; i < index; i++) { System.out.print("----"); } System.out.println("经理:" + this.name); leaders.forEach(leader -> { leader.display(index+1); }); }}Leader 为组长类,组长下有多个工程师 engineers。 ...

April 24, 2019 · 4 min · jiezi

搞懂依赖注入, 用 PHP 手写简易 IOC 容器

前言好的设计会提高程序的可复用性和可维护性,也间接的提高了开发人员的生产力。今天,我们就来说一下在很多框架中都使用的依赖注入。 一些概念要搞清楚什么是依赖注入如何依赖注入,首先我们要明确一些概念。 DIP (Dependence Inversion Principle) 依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。 IOC (Inversion of Control) 控制反转:遵循依赖倒置原则的一种代码设计方案,依赖的创建 (控制) 由主动变为被动 (反转)。 DI (Dependency Injection) 依赖注入:控制反转的一种具体实现方法。通过参数的方式从外部传入依赖,将依赖的创建由主动变为被动 (实现了控制反转)。 光说理论有点不好理解,我们用代码举个例子。 首先,我们看依赖没有倒置时的一段代码: class Controller{ protected $service; public function __construct() { // 主动创建依赖 $this->service = new Service(12, 13); } }class Service{ protected $model; protected $count; public function __construct($param1, $param2) { $this->count = $param1 + $param2; // 主动创建依赖 $this->model = new Model('test_table'); }}class Model{ protected $table; public function __construct($table) { $this->table = $table; }}$controller = new Controller;上述代码的依赖关系是 Controller 依赖 Service,Service 依赖 Model。从控制的角度来看,Controller 主动创建依赖 Service,Service 主动创建依赖 Model。依赖是由需求方内部产生的,需求方需要关心依赖的具体实现。这样的设计使代码耦合性变高,每次底层发生改变(如参数变动),顶层就必须修改代码。 ...

April 22, 2019 · 4 min · jiezi

结构型模式:桥接模式

文章首发:结构型模式:桥接模式 七大结构型模式之二:桥接模式。简介姓名 :桥接模式 英文名 :Bridge Pattern 价值观 :解耦靠我 个人介绍 :Decouple an abstraction from its implementation so that the two can vary independently.将抽象和实现解耦,使得两者可以独立地变化。(来自《设计模式之禅》) 你要的故事现在手机二分天下,安卓手机和苹果手机目前占有率高居 98.45%,其中安卓手机占有率为 70.21%,苹果手机占有率为 28.24%,如下图所示。 (数据从 netmarketshare 来) 因为有这 2 个系统,所以很多软件商都不得不开发 2 个系统的 APP。我们就拿这个案例来讲,目前手机有安卓手机和苹果手机,软件有谷歌浏览器和火狐浏览器,通过手机打开软件这一过程来讲讲桥接模式。 从个人介绍可见,需要抽象化和实现化,然后使用桥接模式将抽象和实现解耦。 抽象化:把一类对象共有的东西抽象到一个类里面,该类作为这类对象的基类。在这里我们可以抽象化的便是手机。 实现化:将接口或抽象类的未实现的方法进行实现。在这里我们可以实现化的就是软件。 将抽象和实现解耦:有了上面的抽象化和实现化,通过桥接模式来实现解耦。在这里,我们把打开软件 open() 放到软件实现中,而抽象的手机利用模板方法模式定义 openSoftware() 供手机子类去实现,手机子类也是调用软件的 open() 方法,并没有自己实现打开逻辑,也就是解耦了这个打开软件过程。 下面给出案例的代码。 Phone 手机抽象类代码。属性 system 代表系统名称,software 代表要打开的软件,openSoftware() 对外提供打开软件的方法。 abstract class Phone { private String system; private Software software; public abstract void openSoftware(); public String getSystem() { return system; } public void setSystem(String system) { this.system = system; } public Software getSoftware() { return software; } public void setSoftware(Software software) { this.software = software; }}AndroidPhone 安卓系统手机代码。 ...

April 22, 2019 · 2 min · jiezi

我眼中设计模式的六大原则

设计模式有六大设计原则,每种设计模式都都绕不开这六个原则。 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。这个原则讲在类(接口)的设计上,一个类所承担的职责一定要单一。但实际中,职责粒度的划分是很不明确的,没有绝对的到哪一粒度就算是满足单一了。反之,过度的考虑单一职责,会引起类的剧增。所以并不必拘泥于类的单一职责,不过于复杂即可。另外,单一职责也可用与方法设计的考虑,比如一个方法利用传入type加switch的方式,写了大段分支代码,不如拆分方法。 里氏替换原则:子类必须能够替代掉父类。这个原则看起来想当然,实际使用中药避免一个错误,在父类的业务逻辑中用instanceof判断是否满足子类类型。 依赖倒转原则:高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。举个实际使用的例,业务层使用底层数据库,如果强依赖于低层实现,那么业务层复用将会十分不便。应该的实现方式是底层用接口实现,上层依赖其接口。典型的例子就是spring+hibernate开发中实现的dao层,而且,hibernate作为orm框架,在努力隔离低层数据库,而spring也用jpa标准,降低对hibernate的耦合,以便能随时替换orm框架。 接口隔离原则:客户端不应该依赖它不需要的接口。有四层含义1、接口尽量小,但拆分接口时先满足单一原则,如果已经粒度够小,不必拆分2、接口要高内聚3、定制服务4、接口设计时有限度的,避免过度设计接口隔离和单一职责全凭经验应用。。。 迪米特法则:最少知识原则。如果两个类不必彼此之间通信,那么这两个类就不应当发生之间的相互作用。比如A依赖B,B依赖C(A -> B -> C),C的业务逻辑只保留在B中,在A中不应该有C的业务逻辑。这个原则在公司的管理上也是一个道理,经理管理组长,组长管理普通员工。日常各类的各类事务,经理只需要从组长处获取必要信息即可,不需要关心员工的工作细节(特殊事件除外。。),这样就能保证从上到下工作高效,避免过多冗余信息的交换,才是一个健康的组织架构。 开放封闭原则:一个软件实体应该对扩展开放,对修改关闭。这一点在程序设计中是非常关键的,对于之后可能存在功能扩展的类,做好抽象,设计合理的接口。而类本身的内容尽可能的避免修改,原则是在对类做扩展时,之前依赖此类的地方不需要做任何修改。 即使抛开所有设计模式,能按照上述六大原则进行代码设计开发,代码质量就能得到很好的保证。所有的设计模式不见得一次性都能记住,不用则不熟。但如果能透传理解上面的原则,可能实际写代码中会不自觉就把某一个模式给实现出来了。 设计模式就是一种编程思路,要明白设计模式并不能提高代码效率。曾有大牛提出设计模式是为了解决面向对象的缺陷而存在的,这个观点本人不反驳,但也并不敢苟同好多人认为“设计模式没有存在的必要”。我眼里的设计模式,是程序员沟通代码思路的媒介,提高代码可读性与可维护性的工具。如果自己有一天也能达到不自觉就用出了各类复杂的设计模式,到那时,希望自己能在代码中留下“此处用了**模式的思路”,胜过大段的代码注释。

April 21, 2019 · 1 min · jiezi

结构型模式:适配器模式

文章首发:结构型模式:适配器模式七大结构型模式之一:适配器模式。简介姓名 :适配器模式英文名 :Adapter Pattern价值观 :老媒人,牵线搭桥个人介绍 :Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。(来自《设计模式之禅》)你要的故事大家有买过港式的 Apple 产品么?在深圳的同学估计买过,毕竟港式的 Apple 产品基本比国内便宜 500 以上。我手机和平板都是在香港买的,买来后这充电器是没法直接充电的,因为港版的电子产品都是英式的插头,而咱们国内是中式的,所以用上港版电子产品的同学免不了要用上这么一个转换器:将英式的插孔转为中式的插孔,方可插入咱家里的插座充电。这个转换器就是今天想讲的适配器。没见过的同学可以看看图片熟悉一下,下图右边为港版苹果手机充电器,插头比较大,左边为某品牌转换器,插头为中国家用标准形状。下图为使用时的图片在这描述一下这个场景。用港式插头要在国内充电,因为插头和插座大小对不上,所以需要加一个适配器,这个适配器充当插头和插座,它的插头可以插入国内标准的插座,它的插座可以插入港式标准的插头,这样子就可以用港式充电器在国内为手机充电。下面用适配器模式代码实现这个场景。首先需要找到被适配的对象是什么?在这里我们的被适配对象是英式充电器。/** * 英式充电器 /class BritishCharger { public void chargeByBritishStandard(){ System.out.println(“用英式充电器充电”); }}在这个场景的目的是什么?在中国为港式手机充电,因此目的是让英式充电器能够在中国标准的插座充电。/* * 使用中式插座充电 /interface Target { void chargeByChineseStandard();}接下来是这个设计模式的主角:适配器。它需要连接中式插座以及英式充电器,在中间做适配功能。/* * 充电器适配器 */class ChargerAdapter implements Target { private BritishCharger britishCharger; public ChargerAdapter(BritishCharger britishCharger) { this.britishCharger = britishCharger; } @Override public void chargeByChineseStandard() { System.out.println(“使用中英式插头转换器”); britishCharger.chargeByBritishStandard(); }}上面是适配器模式的一个简单的例子,要学习适配器模式也可以看看 Java 的 IO 实现源码,里面是应用适配器模式的官方很好的代码。总结适配器很好的将 2 个无法关联的类结合起来,在中间起桥梁作用。另外新增适配器代码不会影响原来被适配者的正常使用,他们可以一起被使用。在工作中和外部系统对接的时候,大可能外部系统的数据格式和自己系统的数据格式并不相同,这时候就可以利用适配器模式来实现。推荐阅读行为型模式:访问者模式行为型模式:解释器模式行为型模式:备忘录模式公众号后台回复『大礼包』获取 Java、Python、IOS 等教程加个人微信备注『教程』获取架构师、机器学习等教程 ...

April 17, 2019 · 1 min · jiezi

行为型模式:访问者模式

文章首发:行为型模式:访问者模式十一大行为型模式之十一:访问者模式。简介姓名 :访问者模式英文名 :Visitor Pattern价值观 :来访者便是客,招待就是个人介绍 :Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。(来自《设计模式之禅》)你要的故事先声明一下,下面故事全瞎编的。。。我们是否还记得 N 年前反腐开始的时候,有一段时间提倡官员宴请吃饭只能几菜几汤,不能超出。我记得那会刚读大一,军事理论的老师说到这个问题,也发表了他的一些想法,他觉得这么做比较刻板。今天的故事就和宴请有关。现在中国企业发展越来越大,在社会中担任的责任也越来越大,政府也越来越重视企业,官员去参观企业是常有的事,而企业宴请官员也变得格外的常见。故事的背景就是企业宴请各级官员。不同级别的官员宴请的菜式就不一样,每家企业的菜式丰富程度也不一样。我们这里的访问对象就用 Alibaba 和 Tencent 这 2 家公司,而访问者就用郭嘉领导人和省领导人做举例。这 2 家公司都跟喜来登酒店合作,Alibaba 合作方案是:宴请省领导人则十菜一汤,宴请郭嘉领导人则十四菜两汤;Tencent 合作方案是:宴请省领导人则八菜一汤,宴请郭嘉领导人则十六菜两汤。下面看看如何用访问者模式来实现上面的故事。首先定义一个抽象类:企业。企业有一个共有的特性就是接受上级领导的访问。/** * 企业 /abstract class Company { public abstract void accept(Vistor vistor);}上面故事我们举例了 2 家企业,分别是 Alibaba 和 Tencent,这里实现这 2 家公司的宴请方案,并实现接待访问者方法。Alibaba 宴请郭嘉领导人是十四菜两汤,宴请省领导是十菜一汤。/* * Alibaba 企业 /class AlibabaCompany extends Company { @Override public void accept(Vistor vistor) { vistor.visit(this); } public String entertainProvincialLeader() { return “Alibaba 接待省领导:十菜一汤”; } public String entertainNationalLeader() { return “Alibaba 接待郭嘉领导:十四菜两汤”; }}Tencent 宴请郭嘉领导人是十六菜两汤,宴请省领导是八菜一汤。/* * Tencent 企业 /class TencentCompany extends Company { @Override public void accept(Vistor vistor) { vistor.visit(this); } public String entertainProvincialLeader() { return “Tencent 接待省领导:八菜一汤”; } public String entertainNationalLeader() { return “Tencent 接待郭嘉领导:十六菜两汤”; }}这里定义访问者接口,访问者接口有 2 个方法,分别是访问 Alibaba 企业和访问 Tencent 企业。/* * 访问者接口 /interface Vistor { void visit(AlibabaCompany alibabaCompany); void visit(TencentCompany tencentCompany);}上面故事中有 2 个访问者,一个是郭嘉领导人,另一个是省领导人,因为不同企业对应不同访问者有不同的宴请方案,所以这里访问企业是需要调用对应企业的宴请方式。省领导人访问企业时,需要调用企业对省领导人的宴请方案,为entertainProvincialLeader()/* * 省领导访问 /class ProvincialLeaderVistor implements Vistor { @Override public void visit(AlibabaCompany alibabaCompany) { System.out.println(alibabaCompany.entertainProvincialLeader()); } @Override public void visit(TencentCompany tencentCompany) { System.out.println(tencentCompany.entertainProvincialLeader()); }}郭嘉领导人访问企业时,需要调用企业对郭嘉领导人的宴请方案,为entertainNationalLeader()/* * 郭嘉领导访问 /class NationalLeaderVistor implements Vistor { @Override public void visit(AlibabaCompany alibabaCompany) { System.out.println(alibabaCompany.entertainNationalLeader()); } @Override public void visit(TencentCompany tencentCompany) { System.out.println(tencentCompany.entertainNationalLeader()); }}上面是访问者和被访问者的代码,因为企业是在喜来登酒店宴请领导人,所以这里还需要一个酒店,酒店里面有企业合作的名单,以及负责宴请各路领导的方法提供。/* * 酒店 */class Hotel { private List<Company> companies = new ArrayList<>(); public void entertain(Vistor vistor) { for (Company company : companies) { company.accept(vistor); } } public void add(Company company) { companies.add(company); }}下面提供测试代码,看看运行的结果怎样。public class VisitorTest { public static void main(String[] args) { AlibabaCompany alibabaCompany = new AlibabaCompany(); TencentCompany tencentCompany = new TencentCompany(); ProvincialLeaderVistor provincialLeaderVistor = new ProvincialLeaderVistor(); NationalLeaderVistor nationalLeaderVistor = new NationalLeaderVistor(); Hotel xilaideng = new Hotel(); xilaideng.add(alibabaCompany); xilaideng.add(tencentCompany); xilaideng.entertain(provincialLeaderVistor); xilaideng.entertain(nationalLeaderVistor); }}打印结果:Alibaba 接待省领导:十菜一汤Tencent 接待省领导:八菜一汤Alibaba 接待郭嘉领导:十四菜两汤Tencent 接待郭嘉领导:十六菜两汤完整的访问者模式代码已经呈现,花 1 分钟思考一番,理解整个代码后我们来看看下面的总结。总结访问者模式有比较好的扩展性,看看访问者代码,我们如果要新增一个访问者:市领导人,只需新增市领导人类,然后企业提供招待市领导人的菜式,便可实现。当然也有它不好的地方,就是把被访问者暴露给访问者,使得访问者可以直接了解被访问者的所有东西。明白了优缺点,才能更好的在实际中运用,一般访问者模式运用于要求遍历多个不同的对象的场景。推荐阅读行为型模式:解释器模式行为型模式:观察者模式行为型模式:迭代器模式设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。 ...

April 16, 2019 · 2 min · jiezi

从未这么明白的设计模式(二):观察者模式

<!– more –>本文原创地址,我的博客:https://jsbintask.cn/2019/04/15/designpattern/observer/(食用效果最佳),转载请注明出处!前言观察者模式定义了对象间的一种一对多依赖关系,当一个对象状态发生改变时,观察者们都可以做出相应的更新,使得系统更易于扩展!代码地址:https://github.com/jsbintask22/design-pattern-learning案例小丽长得很漂亮,“天生丽质难自弃”, 是一个不折不扣的"女神"。小丽身边有很多”备胎“,他们通过各种方式添加了小丽的微信,“小豪,小吴”都是其中之一。小丽总是会在朋友圈发布自己的各种生活状态。”备胎们“总是及时并且积极地和女神互动!小丽发现”备胎“小豪不爱互动了,于是删除了“备胎”小豪的微信。小豪发现自己看不了女神动态了。最终死心!小丽认识了新“备胎”小李,于是小李也添加了女神微信。小丽发布自己的朋友圈动态,小李也开始了互动!上面这个过程我们可以抽象出来两个主题,女神小丽,备胎小豪,小吴,小李,我们用代码模拟这个追女神的过程。代码实现V1.0Beauty代表女神,LittleBoy表示备胎,他们时刻在关注着女神的朋友圈,希望获得互动,代码实现如下:public class App { public static void main(String[] args) throws Exception{ Beauty beauty = new Beauty(); // 成功添加了女神微信 LittleBoy littleBoy = new LittleBoy(beauty); // 开始查看女神朋友圈 littleBoy.start(); // 5s后,女神发布了朋友圈。 Thread.sleep(5000L); beauty.publishWechat(); System.in.read(); }}运行结果如下:嗯!似乎很完美! 美中不足的是好像LittleBoy的run方法一直在轮询查看女神朋友圈,它没办法做自己的事情了:这样下去很快他就会失去和女神互动的耐心! 所以我们稍微修改下,让这段代码看起来更加”智能”。V2.0为了不让LittleBoy一直轮询查看女神状态,我们可以修改为女神主动推送她的状态给“备胎们”,这样他们就可以去做其他事情了!public class App { public static void main(String[] args) throws Exception{ Beauty beauty = new Beauty(); LittleBoy littleBoy = new LittleBoy(); // 添加女神微信 beauty.littleBoy = littleBoy; // 发布动态 beauty.publishWechat(); }}嗯!这样一来就智能多了! 女神更新朋友圈后主动推送消息给备胎!备胎不用死守着女神的朋友圈,而是收到消息后自动去查看。所以他们的关系是这样了:但是,现在又有一个新问题!这段代码好像显得不够面向对象,不够专业。女神如果想要新加一个舔狗,就要动女神的逻辑代码。新加了一个备胎之后,不知道如何把自己的动态分享给他(例如上面的active方法,可能“新备胎”没有)。备胎突然舔不动了怎么办了,他不想再收到女神动态了!既然这样,我们把这段代码修改下,让它变得“灵活”,更加“面向对象”些!V3.0既然要灵活,面向对象。我们这么处理:将女神抽象为一个接口,并且她要能够删除备胎,添加备胎,通知备胎。同时我们将备胎抽象为一个接口,他能够在收到女神通知后及时做出反应!Beauty:LittleBoy:它们分别有一个实现:BeautyImpl和LittleBoyImpl:测试代码:public class App { public static void main(String[] args) { Beauty beauty = new BeautyImpl(); LittleBoy boy1 = new LittleBoyImpl(“小豪”); LittleBoy boy2 = new LittleBoyImpl(“小吴”); // 添加两个备胎 beauty.addLittleBoy(boy1); beauty.addLittleBoy(boy2); // 发布朋友圈 beauty.publishWechat(“最美的不是下雨天,是曾和你一起躲过雨的屋檐!”); // 删除备胎1,并且新添加了备胎3 beauty.removeLittleBoy(boy1); beauty.addLittleBoy(msg -> { System.out.println(" 小李:哎哟,不错哦!"); }); // 再次发布朋友圈 beauty.publishWechat(“哪里有彩虹告诉我。。。”); }}嗯!通过面向接口编程完美的解决了上面的问题,现在女神这个类已经变得非常灵活了,仔细观察,我们已经把我们上面的说的案例完全实现!现在它们的关系是这样的:扩展观察者模式这种发布与订阅的思想使用的非常广泛,基本各个框架,思想都能看到它的身影,而jdk中也已经抽象了观察与被观察者:java.util.Observer表示观察者:java.util.Obserable表示被观察者(例如上面的女神):然后美中不足的是,jdk把Observable设计成了一个类,这并不利于扩展! 当然我们仍然可以自己实现接口,就像上面所做的。总结我们从观察者模式特点入手,通过一个案例,一步一步完善了观察着的写法,特点!组后介绍了jdk总已有的实现!关注我,这里只有干货!同系列文章:从未这么明白的设计模式(一):单例模式 ...

April 15, 2019 · 1 min · jiezi

行为型模式:解释器模式

原文首发:行为型模式:解释器模式十一大行为型模式之十:解释器模式。简介姓名 :解释器模式英文名 :Interpreter Pattern价值观 :不懂解释到你懂个人介绍 :Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。(来自《设计模式之禅》)你要的故事解释器顾名思义就是对 2 个不同的表达方式进行转换,让本来不懂的内容解释成看得懂的。比如翻译官就是解释器,把英文翻译成中文,让我们明白外国人说什么。咱们工作中也有很多类似的场景,开发系统避免不了使用数据库,数据库有特定的语法,我们称为 SQL (Structured Query Language),而我们系统开发语言和 SQL 的语法不一样,这中间就需要做一层转换,像把 Java 语言中的 userDao.save(user) 变成 insert into user (name,age) values (‘小明’, 18),这一层转换也可以称为解释器。很多框架实现了这个功能,比如 Hibernate,我们称这些框架为 ORM。今天,我们就来简单的实现 SQL 拼接解释器,通过参数组装成我们要的 SQL 语句。好多开发同学都吐槽工作天天在 CRUD,也就是只干增删改查的活,对于 SQL 我们经常用的也就是这 4 种语法:insert 语句、delete 语句、update 语句、select 语句。这 4 种语法各有不同,也即需要不同的解释器去解析。利用今天要讲的解释器模式,我们来实现一番。解释器模式中,会有一个上下文类,这个类用于给解释器传递参数。这里我们 SQL 解释器需要的参数分别是tableName :数据库名params :修改时更新后的数据wheres :where 语句后的条件class Context { private String tableName; private Map<String, Object> params = new HashMap<>(); private Map<String, Object> wheres = new HashMap<>(); public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public Map<String, Object> getParams() { return params; } public void setParams(Map<String, Object> params) { this.params = params; } public Map<String, Object> getWheres() { return wheres; } public void setWheres(Map<String, Object> wheres) { this.wheres = wheres; }}解释器主角来了,定义 SQL 解释器抽象类,它有一个抽象方法 interpret,通过这个方法来把 context 中的参数解释成对应的 SQL 语句。/** * SQL 解释器 /abstract class SQLExpression { public abstract String interpret(Context context);}我们上面说了 SQL 语句用的比较多的就是 4 种,每一种其实就是一个解释器,因为语法不一样,解释的逻辑也就不一样,我们就利用 SQLExpression 解释器抽象类,来实现 4 个具体的 SQL 解释器,分别如下:Insert SQL 解释器代码实现:/* * Insert SQL 解释器 /class InsertSQLExpression extends SQLExpression { @Override public String interpret(Context context) { StringBuilder insert = new StringBuilder(); insert.append(“insert into “) .append(context.getTableName()); // 解析 key value StringBuilder keys = new StringBuilder(); StringBuilder values = new StringBuilder(); keys.append(”(”); values.append("("); for (String key : context.getParams().keySet()) { keys.append(key).append(","); values.append("’").append(context.getParams().get(key)).append("’,"); } keys = keys.replace(keys.length() - 1, keys.length(), “)”); values = values.replace(values.length() - 1, values.length(), “)”); // 拼接 keys values insert.append(keys) .append(" values “) .append(values); System.out.println(“Insert SQL : " + insert.toString()); return insert.toString(); }}Update SQL 解释器代码实现:/* * Update SQL 解释器 /class UpdateSQLExpression extends SQLExpression { @Override public String interpret(Context context) { StringBuilder update = new StringBuilder(); update.append(“update “) .append(context.getTableName()) .append(” set “); StringBuilder values = new StringBuilder(); for (String key : context.getParams().keySet()) { values.append(key) .append(” = ‘”) .append(context.getParams().get(key)) .append(”’,”); } StringBuilder wheres = new StringBuilder(); wheres.append(" 1 = 1 “); for (String key : context.getWheres().keySet()) { wheres.append(” and “) .append(key) .append(” = ‘") .append(context.getWheres().get(key)) .append("’"); } update.append(values.substring(0, values.length() - 1)) .append(" where “) .append(wheres); System.out.println(“Update SQL : " + update.toString()); return update.toString(); }}Select SQL 解释器代码实现:/* * Select SQL 解释器 /class SelectSQLExpression extends SQLExpression { @Override public String interpret(Context context) { StringBuilder select = new StringBuilder(); select.append(“select * from “) .append(context.getTableName()) .append(” where “) .append(” 1 = 1 “); for (String key : context.getWheres().keySet()) { select.append(” and “) .append(key) .append(” = ‘”) .append(context.getWheres().get(key)) .append(”’”); } System.out.println(“Select SQL : " + select.toString()); return select.toString(); }}Delete SQL 解释器代码实现/* * Delete SQL 解释器 */class DeleteSQLExpression extends SQLExpression { @Override public String interpret(Context context) { StringBuilder delete = new StringBuilder(); delete.append(“delete from “) .append(context.getTableName()) .append(” where “) .append(” 1 = 1”); for (String key : context.getWheres().keySet()) { delete.append(” and “) .append(key) .append(” = ‘") .append(context.getWheres().get(key)) .append("’"); } System.out.println(“Delete SQL : " + delete.toString()); return delete.toString(); }}测试代码public class InterpreterTest { public static void main(String[] args) { Context context = new Context(); context.setTableName(“user”); // Insert SQL Map<String, Object> params = new HashMap<>(); params.put(“name”, “小明”); params.put(“job”, “Java 工程师”); context.setParams(params); SQLExpression sqlExpression = new InsertSQLExpression(); String sql = sqlExpression.interpret(context); // Delete SQL Map<String, Object> wheres = new HashMap<>(); wheres.put(“name”, “小明”); context.setParams(null); context.setWheres(wheres); sqlExpression = new DeleteSQLExpression(); sql = sqlExpression.interpret(context); // Update SQL params = new HashMap<>(); params.put(“job”, “Java 高级工程师”); wheres = new HashMap<>(); wheres.put(“name”, “小明”); context.setParams(params); context.setWheres(wheres); sqlExpression = new UpdateSQLExpression(); sql = sqlExpression.interpret(context); // Select SQL wheres = new HashMap<>(); wheres.put(“name”, “小明”); context.setParams(null); context.setWheres(wheres); sqlExpression = new SelectSQLExpression(); sql = sqlExpression.interpret(context); }}打印结果:Insert SQL : insert into user(name,job) values (‘小明’,‘Java 工程师’)Delete SQL : delete from user where 1 = 1 and name = ‘小明’Update SQL : update user set job = ‘Java 高级工程师’ where 1 = 1 and name = ‘小明’Select SQL : select * from user where 1 = 1 and name = ‘小明’上面实现了整个解释器模式的代码,其实咱们在开发中,SQL 解析没有这么去实现,更多是用一个工具类把上面的各个 SQL 解释器的逻辑代码分别实现在不同方法中,如下代码所示。因为咱们可以预见的就这 4 种语法类型,基本上不用什么扩展,用一个工具类就足够了。class SQLUtil { public static String insert(String tableName, Map<String, Object> params) { StringBuilder insert = new StringBuilder(); insert.append(“insert into “) .append(tableName); // 解析 key value StringBuilder keys = new StringBuilder(); StringBuilder values = new StringBuilder(); keys.append(”(”); values.append(”("); for (String key : params.keySet()) { keys.append(key).append(","); values.append("’").append(params.get(key)).append("’,"); } keys = keys.replace(keys.length() - 1, keys.length(), “)”); values = values.replace(values.length() - 1, values.length(), “)”); // 拼接 keys values insert.append(keys) .append(" values “) .append(values); System.out.println(“Insert SQL : " + insert.toString()); return insert.toString(); } public static String update(String tableName, Map<String, Object> params, Map<String, Object> wheres) { StringBuilder update = new StringBuilder(); update.append(“update “) .append(tableName) .append(” set “); StringBuilder values = new StringBuilder(); for (String key : params.keySet()) { values.append(key) .append(” = ‘”) .append(params.get(key)) .append(”’,”); } StringBuilder wheresStr = new StringBuilder(); wheresStr.append(" 1 = 1 “); for (String key : wheres.keySet()) { wheresStr.append(” and “) .append(key) .append(” = ‘") .append(wheres.get(key)) .append("’"); } update.append(values.substring(0, values.length() - 1)) .append(" where “) .append(wheresStr); System.out.println(“Update SQL : " + update.toString()); return update.toString(); } public static String select(String tableName, Map<String, Object> wheres) { StringBuilder select = new StringBuilder(); select.append(“select * from “) .append(tableName) .append(” where “) .append(” 1 = 1 “); for (String key : wheres.keySet()) { select.append(” and “) .append(key) .append(” = ‘”) .append(wheres.get(key)) .append(”’”); } System.out.println(“Select SQL : " + select.toString()); return select.toString(); } public static String delete(String tableName, Map<String, Object> wheres) { StringBuilder delete = new StringBuilder(); delete.append(“delete from “) .append(tableName) .append(” where “) .append(” 1 = 1”); for (String key : wheres.keySet()) { delete.append(” and “) .append(key) .append(” = ‘") .append(wheres.get(key)) .append("’"); } System.out.println(“Delete SQL : " + delete.toString()); return delete.toString(); }}总结上面用解释器模式实现了 SQL 解释器,然后又指明了实际上咱们开发中大多数是直接一个 SQLUtil 工具类就搞定,并不是说解释器模式没用,想表达的观点是:解释器在工作中很少使用,工作中我们一般遵循的是能用就好策略,满足当前需求,加上一些易扩展性就足够了。解释器模式有比较大的扩展性,就如上面,再加上个建表语句 create table 只需要加一个 CreateTableSQLExpression 就可以轻松实现,不用去改动其他解释器代码。今天的解释器就到讲到这。觉得不错点个赞鼓励鼓励一下。推荐阅读行为型模式:备忘录模式行为型模式:观察者模式行为型模式:迭代器模式设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。 ...

April 15, 2019 · 5 min · jiezi

????JavaScript设计模式实践:18份笔记、例子和源码????

背景介绍之前在阅读《JavaScript设计模式和开发实践》这本书的时候,收货颇丰,学习了设计模式在很多场景下的应用。但也是因为书上场景过多,所以当记不清某一种设计模式的时候,翻书温习复杂案例的成本是相对较高的。有时候,只需要一段经典、简洁的demo就可以迅速回顾起精髓,在快速业务开发中,这是个比较经济的做法。除此之外,当主要工作语言发生变化的时候(例如:js -> python),简洁的demo更能帮助开发者快速回忆某种设计模式的精髓和实现思路,方便开发者根据语言特性再做实现。因此,对于比较重要的18种设计模式,我都挑选了它的一种经典应用,并且尽量使用ES6的语法和编程习惯来进行实现。 前10个设计模式还提供了Python3的实现版本(后来比较忙,遂放弃)。文章地址一共记录了18个设计模式,部分文章发到了掘金,由于精力有限,后面几篇文章就直接放在了Github仓库 / 个人博客单例模式:https://godbmw.com/passages/2018-10-23-singleton-pattern/策略模式: https://godbmw.com/passages/2018-10-25-stragegy-pattern/代理模式: https://godbmw.com/passages/2018-11-01-proxy-pattern/迭代器模式: https://godbmw.com/passages/2018-11-06-iter-pattern/订阅-发布模式: https://godbmw.com/passages/2018-11-18-publish-subscribe-pattern/命令模式: https://godbmw.com/passages/2018-11-25-command-pattern/组合模式: https://godbmw.com/passages/2018-12-12-composite-pattern/享元模式:https://godbmw.com/passages/2018-12-16-flyweight-pattern/责任链模式: https://godbmw.com/passages/2019-01-07-chain-of-responsibility-pattern/装饰者模式: https://godbmw.com/passages/2019-01-12-decorator-pattern/状态模式: https://godbmw.com/passages/2019-01-16-state-pattern/适配器模式: https://godbmw.com/passages/2019-01-17-adapter-pattern/桥接模式: https://godbmw.com/passages/2019-01-19-bridge-pattern/解释器模式: https://godbmw.com/passages/2019-01-25-interpreter-pattern/备忘录模式: https://godbmw.com/passages/2019-01-26-memento-pattern/模板模式: https://godbmw.com/passages/2019-01-31-template-pattern/工厂模式: https://godbmw.com/passages/2019-03-31-factory-pattern/抽象工厂模式: https://godbmw.com/passages/2019-04-01-abstract-factory-pattern/放在最后其实整理这些的原因还有一个,就是为了准备今年春招的面试。然后过了腾讯的校招和阿里的前三面发现,竟然没有专门问到设计模式相关知识!但回首看,系统地学习、理智地使用设计模式(不是为了用而用),确实能提升代码的可读性,实现业务解耦。而在写这些文章的过程中,每种设计模式自己也是会找很多的实现(包括不限于python、java、c++)来参考,探索式学习还是蛮有趣的。尽管如此,有2篇文章的瑕疵还是很多,希望您抱着交流的心态来阅读,如有不当,欢迎指出、共同提升。

April 11, 2019 · 1 min · jiezi

设计模式-06-组合模式

组合模式概述树形结构在项目中很经常会碰到,当树形结构变得越来越大之后会难以管理。组合模式允许你将对象组合成树形结构来表现“整体/部分”的层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。树里面包含了组合以及个别的对象。实现本例中,用组合模式来实现导航栏。可以有多级导航栏和二级导航栏。总结与分析使用组合模式,可以将相同的操作应用在组合和个别的对象上,换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。代码实现:组合模式(php/go)

April 11, 2019 · 1 min · jiezi

设计模式-07-装饰者模式

装饰者模式概述当对象需要添加一些功能,比如在表单的组件添加验证功能,为咖啡添加配料,为窗口添加滚动条等等.此时如果使用继承的话,会产生很多子类,不好管理,而且,在项目越来越大的时候会出现类爆炸.使用装饰者模式,使用组合的形式构造对象,比使用继承更加灵活简单,也更加容易管理.实现此处实现为咖啡添加配料.有咖啡材料,现调制摩卡咖啡等.使用Beverage抽象类,咖啡和配料分别继承Beverage类,并实现里面的抽象方法Cost和GetDecription.配料的构造方法传入咖啡类,通过在配料的构造函数里组合配料与咖啡原料实现装饰者模式.每个配料(装饰者)都有(包装)一个组件,在装饰者里保存一个原料的引用就能实现咖啡被配料包(装饰)起来.总结与分析装饰者模式采用组合的构建方式,大大减少了类的数量,也打破了扩展功能一定要使用继承的思维惯性.但是装饰者模式会产生过多的小类,过度地使用会让程序变得更复杂.代码实现:装饰者模式(php/go)

April 11, 2019 · 1 min · jiezi

设计模式-04-职责链模式

职责链模式概述职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。实现使用数据过滤类来实现职责链模式。用户传递数据给FilterChain类,但是数据具体会被哪个过滤器处理客户并不知道,FilterChain最终会返回被过滤后的数据给用户。总结与分析职责链模式让请求者和接收者解耦,从而可以动态地切换和组合接收者代码实现:职责链模式(php/go)

April 11, 2019 · 1 min · jiezi

设计模式-02-桥接模式

桥接模式概述桥接模式将抽象部分与它的实现部分分离,使他们都可以独立地变化。通俗地说,桥接就是在不同的东西之间搭一个桥,让它们能够连接起来,可以相互通讯和使用。在桥接模式中的桥接是在被分离的抽象部分和实现部分之间搭一个桥。为了达到让抽象部分和实现部分分离开,而且在抽象部分实现的时候,还是需要使用具体的实现,可以使用桥接模式来实现。这里的桥接,就是让抽象部分拥有实现部分的接口对象,就桥接上了。实现使用发送信息的例子来实现桥接模式。信息的发送方式如:手机信息、普通信息、Email信息作为抽象部分,信息的分类如:普通信息、紧急信息、加急信息作为具体实现部分。总结与分析桥接模式是用来解决有两个变化纬度的情况下,如何灵活地扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独地去变化,最后通过对象组合的方式,把两个纬度组合起来。桥接模式也从侧面体现了使用对象组合的方式比继承要来得更灵活。代码实现:桥接模式(php/go)

April 10, 2019 · 1 min · jiezi

设计模式-03-生成器模式

生成器模式概述生成器模式将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。生成器模式的主要功能是构建复杂的产品,而且是细化地、分步骤地构建产品,也就是说生成器模式重在一步一步解决构建复杂对象的问题。实现使用文件的导出来实现生成器模式。文件的导出有普通文本和xml等文件格式。生成器作为一个接口,不同的具体生成器具体实现接口的方法。还有一个指导者负责整体构建的算法部分,是相对不变的部分。总结与分析生成器模式构建对象的过程是统一的、固定不变的,变化的部分放到生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。生成器模式的重心在于分离构建方法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。生成器模式跟模板方法模式很像,但是模板方法模式主要是用来定义算法的骨架,把算法中某些步骤延迟到子类中实现。生成器模式是用“指导者”来定义整体的构建算法,把算法中某些涉及到具体部件对象的创建和装配的功能,委托给具体的生成器来实现。代码实现:生成器模式(php/go)

April 10, 2019 · 1 min · jiezi

设计模式-01-适配器模式

适配器模式概述在开发过程会遇到有两个项目,A项目想调用B项目的接口,然而两个项目并没有做兼容。适配器模式就将一个类的接口,转换成客户期望的另一个接口。适配器模式就好比IPhone手机的转换器一样。还有Javachoking的swing库也有很多Adapter也是适配器模式应用的场景。结构适配器模式结构图实现实现使用适配Log类。当Log实现了保存到文件的功能后,客户想实现保存到数据库,使用适配器模式可以实现。总结与分析适配器模式的主要目的是组合两个不相干的类,在不改变原有系统的基础上,提供新的接口服务。代码实现:适配器模式(php/go)

April 10, 2019 · 1 min · jiezi

javascript设计模式学习笔记之迭代器模式

迭代器模式顺序访问一个对象的 对象的内部可分为内部迭代器 和 外部迭代器内部迭代器就是常见的 forEach(), 或者 $.each()function forEach(arr, callback) { var i = 0, len = arr.length; for (; i < len; i++) { callback && callback(arr[i]) }}外部迭代器ES6 实现了 Iterator// 简单 实现 Iteratorlet Iterator = function(obj) { let current = 0; let next = function() { current += 1; } let isDone = function() { return current > obj.length; } let getCurrentItem = function() { return obj[current]; } return { next: next, isDone: isDone, getCurrentItem: getCurrentItem, length: obj.length }} ...

April 9, 2019 · 1 min · jiezi

js面试之14种设计模式 (6)

序列文章JS面试之函数(1)JS面试之对象(2)JS面试之数组的几个不low操作(3)JS面试之http0.9~3.0对比分析(4)JS面试之数据结构与算法 (5)前言设计模式如果应用到项目中,可以实现代码的复用和解耦,提高代码质量。 本文主要介绍14种设计模式写UI组件,封装框架必备1.简单工厂模式1.定义:又叫静态工厂方法,就是创建对象,并赋予属性和方法2.应用:抽取类相同的属性和方法封装到对象上3.代码: let UserFactory = function (role) { function User(opt) { this.name = opt.name; this.viewPage = opt.viewPage; } switch (role) { case ‘superAdmin’: return new User(superAdmin); break; case ‘admin’: return new User(admin); break; case ‘user’: return new User(user); break; default: throw new Error(‘参数错误, 可选参数:superAdmin、admin、user’) }}//调用let superAdmin = UserFactory(‘superAdmin’);let admin = UserFactory(‘admin’) let normalUser = UserFactory(‘user’)//最后得到角色,可以调用2.工厂方法模式1.定义:对产品类的抽象使其创建业务主要负责用于创建多类产品的实例2.应用:创建实例3.代码:var Factory=function(type,content){ if(this instanceof Factory){ var s=new thistype; return s; }else{ return new Factory(type,content); }}//工厂原型中设置创建类型数据对象的属性Factory.prototype={ Java:function(content){ console.log(‘Java值为’,content); }, PHP:function(content){ console.log(‘PHP值为’,content); }, Python:function(content){ console.log(‘Python值为’,content); },}//测试用例Factory(‘Python’,‘我是Python’);3.原型模式1.定义:设置函数的原型属性2.应用:实现继承3.代码:function Animal (name) { // 属性 this.name = name || ‘Animal’; // 实例方法 this.sleep = function(){ console.log(this.name + ‘正在睡觉!’); }}// 原型方法Animal.prototype.eat = function(food) { console.log(this.name + ‘正在吃:’ + food);};function Cat(){ }Cat.prototype = new Animal();Cat.prototype.name = ‘cat’;// Test Codevar cat = new Cat();console.log(cat.name);//catconsole.log(cat.eat(‘fish’));//cat正在吃:fish undefinedconsole.log(cat.sleep());//cat正在睡觉! undefinedconsole.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true 4.单例模式1.定义:只允许被实例化依次的类2.应用:提供一个命名空间3.代码:let singleCase = function(name){ this.name = name;};singleCase.prototype.getName = function(){ return this.name;}// 获取实例对象let getInstance = (function() { var instance = null; return function(name) { if(!instance) {//相当于一个一次性阀门,只能实例化一次 instance = new singleCase(name); } return instance; }})();// 测试单体模式的实例,所以one===twolet one = getInstance(“one”);let two = getInstance(“two”); 5.外观模式1.定义:为子系统中的一组接口提供一个一致的界面2.应用:简化复杂接口3.代码:外观模式6.适配器模式1.定义:将一个接口转换成客户端需要的接口而不需要去修改客户端代码,使得不兼容的代码可以一起工作2.应用:适配函数参数3.代码:适配器模式7.装饰者模式1.定义:不改变原对象的基础上,给对象添加属性或方法2.代码let decorator=function(input,fn){ //获取事件源 let input=document.getElementById(input); //若事件源已经绑定事件 if(typeof input.onclick==‘function’){ //缓存事件源原有的回调函数 let oldClickFn=input.onclick; //为事件源定义新事件 input.onclick=function(){ //事件源原有回调函数 oldClickFn(); //执行事件源新增回调函数 fn(); } }else{ //未绑定绑定 input.onclick=fn; }}//测试用例decorator(’textInp’,function(){ console.log(‘文本框执行啦’);})decorator(‘btn’,function(){ console.log(‘按钮执行啦’);})8.桥接模式1.定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化2.代码桥接模式9.模块方法模式1.定义:定义一个模板,供以后传不同参数调用2.代码:模块方法模式10.观察者模式1.作用:解决类与对象,对象与对象之间的耦合2.代码:let Observer= (function(){ let _message={}; return { //注册接口, //1.作用:将订阅者注册的消息推入到消息队列 //2.参数:所以要传两个参数,消息类型和处理动作, //3.消息不存在重新创建,存在将消息推入到执行方法 regist:function(type,fn){ //如果消息不存在,创建 if(typeof _message[type]===‘undefined’){ _message[type]=[fn]; }else{ //将消息推入到消息的执行动作 _message[type].push(fn); } }, //发布信息接口 //1.作用:观察这发布消息将所有订阅的消息一次执行 //2.参数:消息类型和动作执行传递参数 //3.消息类型参数必须校验 fire:function(type,args){ //如果消息没有注册,则返回 if(!_message[type]) return; //定义消息信息 var events={ type:type, //消息类型 args:args||{} //消息携带数据 }, i=0, len=_message[type].length; //遍历消息 for(;i<len;i++){ //依次执行注册消息 _message[type][i].call(this,events); } }, //移除信息接口 //1.作用:将订阅者注销消息从消息队列清除 //2.参数:消息类型和执行的动作 //3.消息参数校验 remove:function(type,fn){ //如果消息动作队列存在 if(_message[type] instanceof Array){ //从最后一个消息动作序遍历 var i=_message[type].length-1; for(;i>=0;i–){ //如果存在该动作在消息队列中移除 _message[type][i]===fn&&_message[type].splice(i,1); } } } } })()//测试用例 //1.订阅消息 Observer.regist(’test’,function(e){ console.log(e.type,e.args.msg); }) //2.发布消息 Observer.fire(’test’,{msg:‘传递参数1’}); Observer.fire(’test’,{msg:‘传递参数2’}); Observer.fire(’test’,{msg:‘传递参数3’});11.状态模式1.定义:一个对象状态改变会导致行为变化2.作用:解决复杂的if判断3.代码状态模式12.策略模式1.定义:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户2.代码策略模式13.访问模式1.定义:通过继承封装一些该数据类型不具备的属性,2.作用:让对象具备数组的操作方法3.代码:访问者模式14.中介者模式1.定义:设置一个中间层,处理对象之间的交互2.代码:中介者模式 ...

April 9, 2019 · 2 min · jiezi

设计模式-工厂模式的演变过程

阅读须知提前了解设计模式的 六大原则,它们代码演化的 目标 和 驱动力。初始阶段需求:将网站的报错信息通过 Email 发送给管理员。需求背景:网站报500类错误时,管理员和开发人员并不能实时知道,等查看日志时或用户打电话过来返回问题时,有可能已经造成了极大的不良影响。so,开发一个实时通知功能,有问题早发现早治疗,岂不美哉?内心想法:这简单,写个异常处理器,配置到系统中进行监听,渲染时走 Email 类发出去。一顿过程化编程操作猛如虎…// Email 类class Email { // 发送 public function send(){}}// 错误异常处理器class ErrorHandler { // 渲染异常和错误 protected function renderException() { $prepare[‘host’] = ‘smtp.qq.com’; $prepare[‘port’] = 587; $prepare[’encryption’] = ’tls’; $prepare[‘username’] = ‘QQ 号’; $prepare[‘password’] = ‘授权码’; // … 艰辛复杂的对象初始化工作 $Mailer = new Mail($prepare); // 设置发送信息 $Mailer->setFrom(’noreply@domain.com’) ->setTo(‘admin@domain.com’) ->setSubject(‘报错了!’) ->setTextBody(‘具体的报错内容’); // 嗖~,搞定! $Mailer->send(); }}Gof: 嗯…这段代码,很头疼,违反了…很多原则啊。ErrorHandler 作为客户端,想发送邮件出去,却参与了邮件发送对象的初始化工作,身为客户端很累的,不符合单一职责原则。ErrorHandler 也知道了邮件发送对象的创建方法,但是它知道这个干吗? 也不符合迪米特原则。但是,如果他们的产品人员提到需求“止步于此”的话,这样写也行。如果开发人员“积极进取”的话,代码可以多往下走一个阶段,因为…太过程化编程了!第二阶段需求:有人退订单了或下单了长时间没有付款的客户,邮件通知到客服。需求背景:让客服跟进一下异常订单的情况,知己知彼,百战不殆嘛。内心想法:这简单,写个订单监听器,配置到系统中进行监听,需要的时候走 EMail 类发出去,已经搞过报错信息通知,这个简单。又在另一处一顿过程化编程操作猛如虎…// Email 类class Email { // 发送 public function send(){}}// 订单监听器class OrderHandler { // 通知 protected function notifly() { $prepare[‘host’] = ‘smtp.qq.com’; $prepare[‘port’] = 587; $prepare[’encryption’] = ’tls’; $prepare[‘username’] = ‘QQ 号’; $prepare[‘password’] = ‘授权码’; // … 艰辛复杂的对象初始化工作 $Mailer = new Email($prepare); // 设置发送信息 $Mailer->setFrom(’noreply@domain.com’) ->setTo(‘service@domain.com’) ->setSubject(‘有异常订单!’) ->setTextBody(‘具体的订单信息’); // 发送,搞定! $Mailer->send(); }}Gof:这就看不过过去了,可复用性太差了,更换个邮件配置还得改多处。需要优化一下了。内心想法: OK,封装一下,封装到一处。// EMail 类class Email { private static $_instance = null; private function __construct(){} private function __clone(){} // 获取对象 public static function getInstance() { if( self::$_instance == null ){ $prepare[‘host’] = ‘smtp.qq.com’; $prepare[‘port’] = 587; $prepare[’encryption’] = ’tls’; $prepare[‘username’] = ‘QQ 号’; $prepare[‘password’] = ‘授权码’; // … 艰辛复杂的对象初始化工作 self::$_instance = new self($prepare); } return self::$_instance; } // 设置消息体 public function initMessage($title, $content) { // 设置消息 $this->setFrom(’noreply@domain.com’) ->setTo(‘service@domain.com’) ->setSubject($title) ->setTextBody($content); } // 嗖~ public function send(){}}// 订单监听器class OrderHandler { // 通知 protected function notifly() { $Mailer = Email::getInstance(); // 设置发送信息 $Mailer->initMessage(‘有异常订单!’, ‘具体的订单信息’); // 嗖~ $Mailer->send(); }}// 错误异常处理器class ErrorHandler { // 渲染异常和错误 protected function renderException() { $Mailer = Email::getInstance(); // 设置发送信息 $Mailer->initMessage(‘报错了!’, ‘具体的报错内容’); // 嗖~ $Mailer->send(); }}Gof:嗯,孺子可教也。ErrorHandler、OrderHandler 作为客户端,不再关心邮件对象的创建过程,直接拿来就用,符合了单一职责原则和迪米特原则。还将 Email 类做成了单例模式,节省了内存空间,提高了性能,不错!第三阶段需求:切换发送通道,通过钉钉群消息发送,关闭原来的 Email 发送通道。需求背景:邮件通知只通知了相应几个管理员,当有人员变化是还需要改收信人配置,最主要的是邮件提醒也不及时啊,有的人还懒的刷邮件。最近公司启用了钉钉,直接走钉钉群自定义消息,人员变动直接屏蔽在外部,增删群成员就行,消息收取方便了,谁看了过了也能知道,报错信息也从共有知识变成了公共知识,岂不美哉?内心想法:处世多年的经验告诉我 Email 邮件通知类不能删除,万一哪天要再加上 Email 发送功能呢?说不定要通知的人不是内部人员没有钉钉账号呢,或要求钉钉消息和邮件同时发送呢, 删除了还得重写。坚决不能删除。之前邮件类优化了一版,这次钉钉通知类直接一步到位,将实例化过程封装在自己内部。// 钉钉通知类class DingDing { private static $_instance = null; private function __construct(){} private function __clone(){} // 获取对象 public static function getInstance() { if( self::$_instance == null ){ $prepare[‘url’] = ‘https://oapi.dingtalk.com/robot/send?access_token='; $prepare[’token’] = ‘123456abcdefg’; // … 艰辛复杂的对象初始化工作 self::$_instance = new self($prepare); } return self::$_instance; } // 设置消息体 public function initMessage($title, $content) { // 设置消息 $this->contentBody([ “msgtype” => “text”, “text” => [ “content” => “{$title}\n {$content}”, ], ]); } // 发送消息 public function sendMsg(){}}// 错误异常处理器class ErrorHandler { // 渲染异常和错误 protected function renderException() { // 通过配置获取使用的消息通道 $channel = ‘dingding’; switch ($channel) { case “dingding”: $DingDing = DingDing::getInstance(); // 设置消息 $DingDing->initMessage(‘报错了!’, ‘具体的报错内容’); // 发送,搞定! $DingDing ->sendMsg(); break; // case “other”: //… case “email”: default: $Mailer = Email::getInstance(); // 设置发送人 $Mailer->initMessage(‘报错了!’, ‘具体的报错内容’); // 发送,搞定! $Mailer->send(); } }}Gof:《从0到1》告诉我们,0到1很难,1到n却很简单,需求也是这样。有2个通知类型很快就会有多个通知类,到时候 renderException() 将会很臃肿。而且,身为 高层模块 的 ErrorHandler 异常处理器类直接依赖的 底层模块 的 Email邮件通知类 和 DingDing钉钉通知类,也违背了 依赖倒置原则 。每次新增、修改通知类时都需要修改 ErrorHandler 类,也不符合 开发-封闭原则。ErrorHandler 类知道了所有的 消息通知类,但是它其实只要消息通知的功能而已,违背了 迪米特原则。得改!内心想法:通过 依赖倒置原则 我们将 依赖细节(类、对象)改为 依赖抽象(抽象类、接口),对消息通知类进行抽象出 消息通知接口。抽象出接口后,就可以通过 实例化参数 或 方法参数 加上 接口类型 来限制只传入我们需要的对象。不至于开发人员传递对象错误到运行时才报错的状况出现。// 通知接口interface INotify{ // 获取实例 public function getInstance(); // 准备消息体 public function initMessage($title, $content); // 发送消息 public function send();}// 钉钉类class DingDing implements INotify { private $title; private $content; // 获取实例 public function getInstance(){return new self();} // 准备消息体 public function initMessage($title, $content){ $this->title = $title; $this->content = $content; } // 发送消息 public function send(){ echo $this->title. $this->content; }}// EMail 类class Email implements INotify { private $title; private $content; // 获取实例 public function getInstance(){return new self();} // 准备消息体 public function initMessage($title, $content){ $this->title = $title; $this->content = $content; } // 发送消息 public function send(){ echo $this->title. $this->content; }}// 错误异常处理器class ErrorHandler { // 消息通知对象 private $notify; // 初始化时限定传入符合 INotify 接口的类 public function __construct(INotify $notifyObj) { $this->notify = $notifyObj; } // 渲染异常和错误 public function renderException() { // 初始化消息体 $this->notify->initMessage(‘有报错了!’, ‘具体的报错信息’); // 发送消息 $this->notify->send(); }}// 订单监听器class OrderHandler { // 消息通知对象 private $notify; // 初始化时限定传入符合 INotify 接口的类 public function __construct(INotify $notifyObj) { $this->notify = $notifyObj; } // 通知 public function notify() { // 初始化消息体 $this->notify->initMessage(‘有异常订单!’, ‘具体的订单信息’); // 发送消息 $this->notify->send(); }}客户端代码# 错误异常处理器客户端// 通过配置获取使用的消息通道$channelError = ‘dingding’;switch ($channelError) { case “dingding”: $MessageNotify = DingDing::getInstance(); break; case “other1”: //… case “email”: default: $MessageNotify = Email::getInstance();}$ErrorHandler = new ErrorHandler($MessageNotify);$ErrorHandler->renderException();# 订单监听器客户端// 通过配置获取使用的消息通道$channelOrder = ’email’;switch ($channelOrder) { case “dingding”: $MessageNotify = DingDing::getInstance(); break; case “other1”: //… case “email”: default: $MessageNotify = Email::getInstance();}$OrderHandler = new OrderHandler($MessageNotify);$OrderHandler->notify();Gof:高层模块的 ErrorHandler 异常处理器依赖了抽象的 INotify 接口,符合了 依赖倒置原则。当有新的的消息通知需求时直接实现 INotify 接口,并通过 初始化参数 传入即可,不用再修改 ErrorHandler 类,也符合了 开发-封闭原则。通过 初始化参数 传入具体的消息通知对象,ErrorHandler 也不用再关心具体有多少种通知方式,具体用的哪种通知方式,也符合了 迪米特原则。但是还有以下不足,需要进步抽象优化。具体创建哪个消息通知对象的处理出现了重复,需要整合到一处,方便修改和复用。消息通知对象的创建过程放到了消息通知类的内部,多少有点违背了 单一职责原则。如果创建过程很“复杂”,强依赖了外部环境,例如依赖别的类的实例,或直接从具体的数据源(例如:DB,Redis)中读取配置等,将不利于以后的测试和功能迭代,应该将创建过程提到类外部,必要的参数通过初始化参数形式传入。// 通知消息工厂class NotifyFactory{ // 创建通知消息对象 public static function create($channel) { switch ($channel) { case “dingding”: $MessageNotify = DingDing::getInstance(); break; case “other1”: //… case “email”: default: $MessageNotify = Email::getInstance(); } return $MessageNotify; }}# 错误异常处理器客户端// 通过配置获取使用的消息通道$channelError = ‘dingding’;$MessageNotify = NotifyFactory::create($channelError);$ErrorHandler = new ErrorHandler($MessageNotify);$ErrorHandler->renderException();# 订单监听器客户端// 通过配置获取使用的消息通道$channelOrder = ’email’;$MessageNotify = NotifyFactory::create($channelOrder);$OrderHandler = new OrderHandler($MessageNotify);$OrderHandler->notify();等等灯等灯: 现在已经达成了 简单工厂模式。Gof: 嗯不错,消息通知类对外部的依赖被提到了类的外部,这样的好处多啊:方便对类进行自动化测试,例如 DingDing 原来初始化时从 DB 中获取配置进行初始化。单独测试 DingDing 类时,就必须要连上DB数据库,现在需要将配置通过初始化参数传入接口,参数值想从哪取就从哪取。外部依赖变更时,只需要对 NotifyFactory 类进行修改,修改影响范围变小了。不过,还有一个问题,那就是每次新增消息通知类时都需要修改 NotifyFactory 消息通知工厂的代码,往里添加 case 判断,不符合 开发-封闭原则。内心想法:我们可以进一步抽象,将对代码的修改调整为对类的增删上。// 通知消息工厂接口interface IFactory{ // 创建通知消息对象 public function create();}// 钉钉工厂类class DingDingFactory implements IFactory { public function create() { return DingDing::getInstance(); }}// Email工厂类class EmailFactory implements IFactory { public function create() { return Email::getInstance(); }}// Sms 工厂类class SmsFactory implements IFactory { public function create() { return Sms::getInstance(); }}// Sms 类class Sms implements INotify { private $title; private $content; // 获取实例 public function getInstance(){return new self();} // 准备消息体 public function initMessage($title, $content){ $this->title = $title; $this->content = $content; } // 发送消息 public function send(){ echo $this->title. $this->content; }}# 错误异常处理器客户端// 通过配置获取消息通知工厂类名$classError = ‘DingDingFactory’;// $classError = ‘SmsFactory’; 新增预备的短信通知通道$MessageNotify = (new $classError())->create();$ErrorHandler = new ErrorHandler($MessageNotify);$ErrorHandler->renderException();# 订单监听器客户端// 通过配置获取消息通知工厂类名$channelOrder = ‘EmailFactory’;$MessageNotify = (new $channelOrder())->create();$OrderHandler = new OrderHandler($MessageNotify);$OrderHandler->notify();等等灯等灯: 现在已经达成了 工厂方法模式。Gof: 当新增消息通知类时,已不需要修改任何已有代码,只需要新增一个 通知工厂类 和 一个消息通知类 即可。完美!!!此时启用消息通知类的变更被限定在配置文件或数据库数据配置变化上,切换消息通知通道并不需要修改程序代码。第四阶段需求:发送通知时记录一下日志,方便日后查询与统计。需求背景:有了即时通知,但想后期查询或统计怎么办,记录一下日志吧。异常订单比较重要,记录到 MySQL 中,查询异常报错字段比较多,记录的 Elasticsearch 中。内心想法:日志类和消息通知类很像嘛,直接搞成工厂方法模式,哈哈。// 日志接口interface ILog{ // 获取实例 public function getInstance(); // 写日志 public function write();}// Mysql Log 类class MysqlLog implements ILog {}// EalsticsearchLog 类class EalsticsearchLog implements ILog {}// 日志工厂接口interface ILogFactory{ // 创建日志记录对象 public function create();}// Mysql 日志工厂类class MysqlLogFactory implements ILogFactory { public function create() { return MysqlLog::getInstance(); }}// Ealsticsearch 日志工厂类class EalsticsearchLogFactory implements ILogFactory { public function create() { return EalsticsearchLog::getInstance(); }}# 错误异常处理器客户端// 通过配置获取消息通知工厂类名$classError = ‘DingDingFactory’;$MessageNotify = (new $classError())->create();// 通过配置获取日志记录工厂类名$logClassError = ‘EalsticsearchLogFactory’;$Log = (new $logClassError)->create();$ErrorHandler = new ErrorHandler($MessageNotify, $Log);$ErrorHandler->renderException();# 订单监听器客户端// 通过配置获取消息通知工厂类名$channelOrder = ‘EmailFactory’;$MessageNotify = (new $channelOrder())->create();// 通过配置获取日志记录工厂类名$logClassError = ‘MysqlLogFactory’;$Log = (new $logClassError)->create();$OrderHandler = new OrderHandler($MessageNotify, $Log);$OrderHandler->notify();Gof: 新增一个工厂模式并非这么简单就能解决问题的。 目前需求是 消息通知 并 记录日志 ,两者已经是 组合 关系,将这种组合关系下放到客户进行创建那么就不符合 单一职责原则。客户端还知道有2个工厂,2个工厂生产的东西必须搭配在一起才能实现消息通知并记录日志的功能,不符合 迪米特原则。类似买苹果笔记本,有不同的配置,简单那2个配置举例子,cpu 分为 i7 和 i5, 屏幕分为 13 寸 和 15 寸。普通消费者买笔记本会说:“我要个玩游戏爽的笔记本”,店员应该直接给出 i7 + 15 寸配置的机型。如果一个人也是玩游戏,过来直接说:“要个 i7 + 15 寸配置的机型”,那这个人一听就是程序员(知道的太多了!不符合单一职责原则和 迪米特原则)。// 通知消息工厂接口interface IFactory{ // 创建通知消息对象 public function createNotify(); // 创建日志记录对象 public function createLog();}// 异常报错工厂类class ErrorFactory implements IFactory { public function createNotify() { return DingDing::getInstance(); } public function createLog() { return EalsticsearchLog::getInstance(); }}// 异常订单工厂类class OrderFactory implements IFactory { public function createNotify() { return Email::getInstance(); } public function createLog() { return MysqlLog::getInstance(); }}# 错误异常处理器客户端// 通过配置获取异常错误工厂类名$classError = ‘ErrorFactory’;$Factory = new $classError();$ErrorHandler = new ErrorHandler($Factory->createNotify(), $Factory->createLog());$ErrorHandler->renderException();# 订单监听器客户端// 通过配置获取异常订单工厂类名$classError = ‘OrderFactory’;$Factory = new $classError();;$OrderHandler = new OrderHandler($Factory->createNotify(), $Factory->createLog());$OrderHandler->notify();等等灯等灯: 现在已经达成了 抽象工厂方法模式 。 ...

April 6, 2019 · 5 min · jiezi

节流 - 理解,实践与实现

节流(分流),与防抖(去抖)实现原理相似。本文主要讨论节流,镜像文章:防抖 - 理解,实践与实现。分开讨论防抖和节流,主要是为了让一些还不太了解节流防抖的读者能够有针对性地,逐一掌握它们。 如何用代码实现节流也是一个要点。本文采用循序渐进地方式,先绘制一个案例的流程图,再根据流程图的逻辑编写节流功能代码。文章包含多个可交互案例,可通过博客原文实时查看案例。欢迎Star和订阅我的原创前端技术博客。节流案例点击运行案例当鼠标移动时,mousemove事件频繁被触发。上方为未节流模式,每一次mousemove触发都会绘制一个圆点。而下方为节流模式,尽管mosuemove在鼠标移动时被多次触发,但只有在限定时间间隔才会绘制圆点。理解和实现节流通过上方案例,可以基本了解节流的作用: 频繁触发的事件,事件处理函数在一定的时间间隔内只执行一次。不过节流函数是如何做到的? 以上方案例为例,绘制其流程图如下。 核心参数:间隔时长计时器根据流程图的思路实现分流函数:function throttle( func, wait ) { let timer function throttled( …args ) { const self = this if ( timer == null ) { invokeFunc() addTimer() } function addTimer() { timer = setTimeout( () => { clearTimer() }, wait ) } function invokeFunc() { func.apply( self, args ) } } return throttled function clearTimer() { clearTimeout( timer ) timer = null }}接下来,用编写的节流函数实现上方案例点击运行案例应用场景无限的滚动条点击运行案例总结节流和防抖类似,都能有效优化系统性能,不过使用业务场景有所区别:防抖既可用于在多次触发的事件(如文本框逐个输入文字),也可用于在频繁触发的事件(如调整窗口尺寸)。节流多只用在频繁触发的事件(如滚动滚动条)上。感谢你花时间阅读这篇文章。如果你喜欢这篇文章,欢迎点赞、收藏和分享,让更多的人看到这篇文章,这也是对我最大的鼓励和支持! 欢迎Star和订阅我的原创前端技术博客。

April 2, 2019 · 1 min · jiezi

我对SOLID的理解

超前的设计或者过度的设计都不是良好的设计,很多时候我们等到代码在第一次变化的时候可以及时作出反应就够了单一责任原则(The Single Responsibility Principle )根据实际情况,拿捏需求的拆分力度,根据拆分的块,来设计对应的类单一责任原则:我们设计一个类的时候,应该尽量把类的职责单一化;那么我们拿到需求的时候,应该对需求进行分析,再拆分职责,再设计对应的类。在实际工作中吧,其实大部分的程序猿都知道这个道理,主要的阻力是开发时间(偷懒),为了方便处理就不搞太多类了;还是有个原因,就是如果拆分力度大,拆分的太细,那么可能会出现一个简单的需求,你写了好几个类,这样也不是可取的,这种有点过度设计了,没有必要。举个例子吧,我是做iOS的,就说说界面相关的需求吧,比如说现在产品的需求是做一个word功能的属性面板,里面有几个模块:选择字体,设置对齐方式,设置字体颜色,每个模块都是列表(tableView),那么在开发前,就应该拆分好,比如说属性面板(容器类),三个子模块(对应三个类),这样来开发,这就是单一责任的应用了再举个例子,比如这时候要设计一个工厂类,工厂类需要生产A,B产品,还有一个原料的预加工,现在的需求是原料的预加工,A和B都是一样的,也就是说A和B可以共用这个方法,那么这三个功能就都堆在工厂类里面了。这样是违背了单一责任原则了,但是如果需求确实这边简单,比较小需求,那么这样来开发我觉得也没有问题,毕竟对于大部分怼业务的程序猿来说,又快又稳的出货才是王道。如果第二期需求来了,要求改动A产品的预加工方式,那么这时候,A和B就不可以公用一个预加工方法了,那么这个时候,就是要及时作出重新设计的考虑了,而且这时候的考虑可以多考虑一下未来可能的改动。说回刚来产品要求改A产品的预加工需求,如果我们改了工厂类的预加工方法,那么B产品就受影响了。这个就说明了单一责任原则的重要性了,可能降低以后版本迭代中,改动的影响面。对于这个例子,可能一些同学觉得这个很简单就知道我改动预加工方法会影响到B产品,问题是,在比较大项目,复杂的项目中,有时候改动了一个地方,影响到另一个地方是很难发现的,测试阶段发现还好,有时候可能不是很明显,也不是严重的影响,有可能上线了好久才偶然间发现除了类需要遵循单一职责原则,方法也同样需要,一般方法内的代码不能太多开闭原则(The Open Closed Principle)用抽象构建框架,用实现扩展细节开闭原则:就是写的代码即要有开放性,也要有封闭性,比如现在写一个加法需求,如果一开始就写一个加法类,那么后期扩展,有减法,乘法,除法等,那么是不是继续创建新的类,那么这些加减乘除是不是应该会存在共用的东西,那么就可以抽成一个计算类,这个计算类可以做一些共用的约束,比如计算类可以有formula公式方法,result计算结果的方法,然后加减乘除继承于计算类,重写对应的方法即可。那么这样的操作,就是对计算类封闭,但是计算类又对外开放,比如继承他,去实现一些细节问题所以开闭原则说的就是这个意思,用抽象构建框架,用实现扩展细节,比如抽象类(计算类),实现细节由加减乘除子类去做。需要说明的是,对修改关闭不是说软件设计不能做修改,只是尽量不要做不必要的修改。怎么才能做到呢?那就是有相应的扩展性。其实,软件有相应的扩展性是好处,但是不能说每个地方都有扩展。反而造成了代码的臃肿。所以这里的扩展与修改关闭是有限制的。这个结合实际的工作开发,如果我们遇到的需求,都考虑弄个抽象类,都自己考虑了以后的一堆扩展,不是说这个考虑不好,只是在开发上浪费了时间,有可能你的架构,在未来很长时间都没有用上,那么这就形同臃肿的架构设计,所以实际开发,还是要按照实际情况去处理,不要为了达到开闭原则写开闭原则。还有抽象层尽量保持稳定,尽量不修改,因为我们在开发中,修改老旧代码,评估最多的是影响面,如果动到了抽象层,意味着影响面很大里氏替换原则(Liskov Substitution Principle)规范子类的书写规则,实现父类抽象方法,不能覆盖父类的具体方法,以此达到父类的方法不被覆盖和父类可以出现的地方,子类就能出现(意思就是比如一个方法的参数是传父类类型,那么这时候传子类进去,也得是没有问题的)我们设计基类的时候,应该尽量做到基类是抽象的,尽量抽象,如果一个基类的功能够完善,那么这个应该定义为具体类,而不是基类,因为越完善越具体的类,以后子类继承他,子类的扩展性就越差。这就违背了开闭原则。 至于里氏替换原则,说的就是规范一些子类写法的规则,比如子类可以实现父类的抽象方法,但是不能覆盖父类的具体方法子类可以增加自己特有的方法当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。第三和四点不太理解,这有一个详细的问题,但是我没看懂(https://blog.csdn.net/qq_3496…说简单点就是开闭原则就是父类尽量抽象,子类扩展,里氏替换原则就是子类实现的细节怎么来写,怎么规范,还有继承必须确保父类所拥有的性质在子类中仍然成立。接口隔离原则(Interface Segregation Principle)接口尽量细化,不要臃肿,这里接口的意思就是oc的代理,java的interface比如现在接口1有5个方法,类A依赖接口1的1-3个方法,类B依赖接口1的3-5个方法;那么类A依赖接口1就必须实现这5个方法,但是4、5方法对于类A来说是完全没有必要实现的,所以这样的接口1设计就是臃肿的,所以我们应该细化他,变成两个接口,对应类A和B在oc中代理方法有可选的,那么似乎就没有这个问题了,这么看也确实没有,但是@protocol设置@optional的意思是,可以实现,也可以不实现,是非必须的意思。接口隔离原则就是完全不必要实现的,这种情况就需要考虑拆分,细化接口依赖倒置原则(Dependency Inversion Principle)面向抽象(接口)编程接口,抽象,意思就是定义一种规范,协议,自己不实现具体的代码,只是指明了大方向的意思,比如一个公司的老板就是接口,就是抽象的,因为他不用做细节的东西,但是他指定的方向,规范细节就是具体的实现比如在iOS中,协议就是接口,抽象类的抽象方法也是接口,我们常说要面向接口编程,而不是面向实现编程,因为接口是文档的,细节的实现是多变的。我们的编程是追求稳定维护程序的,所以我们要面向接口编程举个例子初级程序猿类primary方法:我的工资是1万薪酬管理类方法:claculate:(primary *)pri那么薪酬管理类可以计算出初级程序猿的工资了,但是有个问题,如果要计算中级程序猿的话,那薪酬管理类不就得再添加方法,因为claculate:(primary *)pri方法的入参是初级程序猿类,那这样不行,以后越来越多岗位不就得总去修改薪酬管理类。所以这时候,我们要面向接口编程定义一个协议@protocol EmployeeDelegate <NSObject>- (void)calculateSalary; @end薪酬管理类- (void)claculate:(id<EmployeeDelegate>)pri;这样每个岗位都实现代理方法,然后薪酬管理类的入参是实现了对应代理方法的类,这样就不用新增岗位,就去改管理类的代码了,这就是面向接口编程的一个例子了总结单一责任原则是最基本的编程要求,一般我们主要一个类一个责任,一个方法一个责任这样写编写代码。关于代码整洁的话,我的理解就是做到抽取代码,方法内容简洁,对应的方法内做对应的事,这样可以方便以后的查找,维护。开闭原则就是说要用抽象搭建框架,实现扩展细节,细节交给具体类或者子类来处理和扩展里氏替换原则就是规范子类的规范;接口隔离原则,接口(这里的接口指oc的协议)细化,不要臃肿;依赖倒置原则,就是面向接口编程我对SOLID之间关系的理解:单一责任原则是最基本的编程原则;开闭原则是整个程序架构的最终目标,里氏替换原则、接口隔离原则,依赖倒置原则都是为了实现开闭原则

March 31, 2019 · 1 min · jiezi

javascript设计模式学习笔记之代理模式

代理模式代理模式是为一个对象提供一个代用品或者占位符, 以便控制对它的访问引入代理模式,其实是为了实现单一职责的面向对象设计原则。虚拟代理将一些开销很大的对象, 延迟到正真需要的时候执行 // 针对大图 增加loading图 // 创建 img var myImage = (function () { var imgNode = document.createElement(‘img’); document.body.appendChild(imgNode); return { // 直接设置 img 的src setSrc: function (src) { imgNode.src = src; } } })(); // 代理对象 var proxyImage = (function () { var img = new Image(); img.onload = function () { // 在代理中等到图片加载完在设置正真的图片地址 myImage.setSrc(this.src); }; return { setSrc: function (src) { myImage.setSrc(’./loading.jpg’); img.src = src; } } })(); proxyImage.setSrc(‘https://xxx.com/realImage.png')缓存代理可以为一些开销大的运算结果提供暂时的存储 // 体现了 单一职责原则, // 原函数 var multi = function () { var a = 1; for (var i = 0, l = arguments.length; i < l; i++) { a = a * arguments[i]; } console.log(a); return a; } // 代理函数 var proxyMulti = (function () { var cache = {}; return function () { var args = Array.prototype.join.call(arguments, ‘,’); if (args in cache) { return cache[args]; } return cache[args] = multi.apply(this, arguments); } })(); proxyMulti(1, 2, 3, 4, 5); proxyMulti(1, 2, 3, 4, 5); ...

March 31, 2019 · 1 min · jiezi

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

背景设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。如果再通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。这些“好的设计”并不是谁发明的,而是早已存在于软件开发中。一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式。GoF(Gang of Four–四人组,《设计模式》几位作者)最大的功绩是把这些“好的设计”从浩瀚的面向对象世界中挑选出来,并且给予它们一个好听又好记的名字。设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,他不是一个死的机制,他是一种思想,一种写代码的形式。每种语言对于各种设计模式都有他们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂方法模式对于javascript。模式应该用在正确的地方。而哪些才算正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道。。模式的社区一直在发展。GoF在1995年提出了23种设计模式,但模式不仅仅局限于这23种,后面增加到了24种。在这20多年的时间里,也许有更多的模式已经被人发现并总结了出来,比如一些JavaScript 图书中会提到模块模式、沙箱模式等。这些“模式”能否被世人公认并流传下来,还有待时间验证。观察者模式(Observer Pattern)观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。发布订阅模式(Pub-Sub Pattern)其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。举一个例子,你在微博上关注了A,同时其他很多人也关注了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(item, index){ item(money) }) } Hunter.prototype.subscribe = function (targrt, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了’ + targrt.name) targrt.list.push(fn) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter hunterMing.subscribe(hunterPeter, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘暂时很忙,不能’) + ‘给予帮助’) }) hunterJin.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) hunterZhang.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) //Peter遇到困难,赏金198寻求帮助 hunterPeter.publish(198) //猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)发布订阅模式: //定义一家猎人工会 //主要功能包括任务发布大厅(topics),以及订阅任务(subscribe),发布任务(publish) let HunterUnion = { type: ‘hunt’, topics: Object.create(null), subscribe: function (topic, fn){ if(!this.topics[topic]){ this.topics[topic] = []; } this.topics[topic].push(fn); }, publish: function (topic, money){ if(!this.topics[topic]) return; for(let fn of this.topics[topic]){ fn(money) } } } //定义一个猎人类 //包括姓名,级别 function Hunter(name, level){ this.name = name this.level = level } //猎人可在猎人工会发布订阅任务 Hunter.prototype.subscribe = function (topic, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了狩猎’ + topic + ‘的任务’) HunterUnion.subscribe(topic, fn) } Hunter.prototype.publish = function (topic, money){ console.log(this.level + ‘猎人’ + this.name + ‘发布了狩猎’ + topic + ‘的任务’) HunterUnion.publish(topic, money) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //小明,小金,小张分别订阅了狩猎tiger的任务 hunterMing.subscribe(’tiger’, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘不’) + ‘接取任务’) }) hunterJin.subscribe(’tiger’, function(money){ console.log(‘小金表示:接取任务’) }) hunterZhang.subscribe(’tiger’, function(money){ console.log(‘小张表示:接取任务’) }) //Peter订阅了狩猎sheep的任务 hunterPeter.subscribe(‘sheep’, function(money){ console.log(‘Peter表示:接取任务’) }) //Peter发布了狩猎tiger的任务 hunterPeter.publish(’tiger’, 198) //猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式比较直接粗暴,但是会造成代码的冗余。而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。观察者模式是不是发布订阅模式网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构。如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。 ...

March 29, 2019 · 2 min · jiezi

设计模式-适配器模式-说明

模式说明定义:将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。类图结构说明目标抽象类:目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。适配器类:适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系适配者类:适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。优点将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。对象适配器模式还有如下优点:一个对象适配器可以把多个不同的适配者适配到同一个目标;可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。缺点对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。对象适配器模式的缺点如下:与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。适用场景系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。代码实现

March 28, 2019 · 1 min · jiezi

行为型模式:备忘录模式

十一大行为型模式之九:备忘录模式。简介姓名 :备忘录模式英文名 :Memento Pattern价值观 :凡事要有备份个人介绍 :Without violating encapsulation,capture and externalize an object’s internal state so that the object can be restored to this state later.在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。(来自《设计模式之禅》)你要的故事点开看这篇文章的各位,都是。。。程序界的大佬。作为程序猿,免不了『上线』这件小事。每逢上线必祭天。。。上线这件事我们很多人都操作过,每家公司有不同的上线流程以及上线的技术能力。按照发布的平台的完善程度大概分为 3 种。发布平台牛逼的公司:只需要按下『一键部署』按钮,就搞定上线,按下『回滚』按钮,就搞定回滚上一个版本。发布平台稍差点的公司:可能就得多个步骤操作了,上线:备份并关闭应用、部署并启动新应用;回滚:关闭新应用、恢复旧应用并启动。没有发布平台的公司:那就全程手工操作,上线:关闭旧应用、复制旧应用到备份空间、复制新应用到部署环境、启动新应用;回滚:关闭新应用、删除新应用、从备份空间复制旧应用到部署环境、启动旧应用。其实通过发布平台完善程度可以侧面反映企业的技术成熟程度。怎么说呢?发布系统的操作难易程度在我们作为程序猿心中,都有一个可接受的范围。假设刚开始是单体应用,一个 tomcat 和一个 war 就搞定,你需要发布平台么?并不需要,咱 ctrl+C、ctrl+V、shutdown、startup 就行,还弄什么发布平台。当系统有多套服务时,每次上线都需要部署 10 个机器的应用,这时你能忍么?要是还是手工操作,那发布一次系统得花费好长时间,要是再搞不好,回滚一次,一晚上都没了;这时就会逼迫开发出一个简易的发布平台,把对多台机器的操作步骤放到发布平台上。当系统是以微服务的架构发展时,每个服务都有上百个实例,那这时就不能简单的把操作步骤搬到发布平台了,还得简化步骤,最终变成上面说的 一键部署 和一键回滚。上面的 3 种我都亲身经历过。。。在刚出来实习时候,就经历了第 3 种情况,因为是单体应用,一个应用搞定所有东西,手动部署已满足要求。到了银行工作,接触到了云平台,那时就只需要一个按钮就唰唰唰的部署了,也是上面说的第 1 种。而现在,正在经历第 2 种发布平台,只是简单的把操作步骤搬到了系统上,目前的情况是机器越来越多,操作步骤没删减的话,每次发布会花费很多时间,这也会去促进开发出更方便使用的发布平台。回到今天的主题,今天讲的是备忘录模式,从字面上理解,就是讲备份东西,有了备份就可以恢复。上面讲了一大堆发布的东西,也是咱们工作中接触蛮多的事情,发布的最核心就是要支持部署新应用以及回滚老应用,回滚特别重要,它能够保证在新应用出现异常的情况下,马上恢复到旧应用可用的状态,减少异常的影响面。发布这东东也很符合备忘录模式,下面通过模拟发布步骤代码来讲备忘录模式。这里讲的不是上面说的第 1 种,这里围绕着第 3 种发布步骤写,不管哪种发布平台,它们的底层都是一样的。先定义应用实例这个类,应用一般会有应用名、版本号信息。/** * 应用实例 /class App { private String content; private String version; public App(String content, String version) { this.content = content; this.version = version; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } @Override public String toString() { return “App{” + “content=’” + content + ‘'’ + “, version=’” + version + ‘'’ + ‘}’; }}定义 AppBackup 来充当备忘录角色,它有一个属性就是 App,也就是备份的应用。/* * 应用备份(充当备忘录角色) /class AppBackup { private App app; public AppBackup(App app) { this.app = app; } public App getApp() { return app; } public void setApp(App app) { this.app = app; }}有了备忘录,也需要一个空间来存放备忘录,并对外提供备忘录。/* * 备份空间 /class Space { private AppBackup appBackup; public AppBackup getAppBackup() { return appBackup; } public void setAppBackup(AppBackup appBackup) { this.appBackup = appBackup; }}有了这些备份机制,还需要有一个程序猿来部署,这位同学需要掌握发布步骤的所有过程,部署新应用以及回滚旧应用。/* * 部署应用的同学 */class Deployer { // 要部署的应用 private App app; public App getApp() { return app; } // 设置部署应用 public void setApp(App app) { this.app = app; } // 创建应用的备份 public AppBackup createAppBackup() { return new AppBackup(app); } // 从备忘录恢复应用 public void setAppBackup(AppBackup appBackup) { this.app = appBackup.getApp(); } // 显示应用的信息 public void showApp() { System.out.println(this.app.toString()); } // 暂停应用 public void stopApp() { System.out.println(“暂停应用:” + this.app.toString()); } // 启动应用 public void startApp() { System.out.println(“启动应用:” + this.app.toString()); }}再献上测试代码。public class MementoTest { public static void main(String[] args) { Deployer deployer = new Deployer(); deployer.setApp(new App(“apply-system”, “1.0.0”)); System.out.println(“1. 暂停旧应用”); deployer.stopApp(); System.out.println(“2. 备份旧应用”); Space space = new Space(); space.setAppBackup(deployer.createAppBackup()); System.out.println(“3. 拷贝新应用到服务器”); deployer.setApp(new App(“apply-system”, “2.0.0”)); deployer.showApp(); System.out.println(“4. 启动新应用”); deployer.startApp(); System.out.println(“5. 有异常,暂停新应用”); deployer.stopApp(); System.out.println(“6. 回滚旧应用,拷贝备份的旧应用到服务器”); deployer.setAppBackup(space.getAppBackup()); deployer.showApp(); System.out.println(“7. 启动备份的旧应用”); deployer.startApp(); }}打印结果:1. 暂停旧应用暂停应用:App{content=‘apply-system’, version=‘1.0.0’}2. 备份旧应用3. 拷贝新应用到服务器App{content=‘apply-system’, version=‘2.0.0’}4. 启动新应用启动应用:App{content=‘apply-system’, version=‘2.0.0’}5. 有异常,暂停新应用暂停应用:App{content=‘apply-system’, version=‘2.0.0’}6. 回滚旧应用,拷贝备份的旧应用到服务器App{content=‘apply-system’, version=‘1.0.0’}7. 启动备份的旧应用启动应用:App{content=‘apply-system’, version=‘1.0.0’}备忘录模式代码实现搞定。有同学会不会觉得挺麻烦的,为什么要有AppBackup?我们看看个人介绍,在对象之外保存状态,AppBackup 就是对象之外的对象,用来保存旧应用。总结备忘录模式定义了一个备份机制。在很多场景都有类似备忘录模式的实现,比如数据库的事务的回滚机制。在平常业务开发中并没有经常使用这个设计模式,但是我们有使用它的思想,比如我们用数据库或者其他中间件做备份数据,其中备份思想是一致的。推荐阅读行为型模式:状态模式行为型模式:观察者模式行为型模式:迭代器模式设计模式系列文章持续更新中,欢迎关注公众号,一起交流学习。 ...

March 27, 2019 · 2 min · jiezi

吃透动态代理,解密spring AOP源码(四)

前面讲到了动态代理的底层原理,接下来我们来看一下aop的动态代理.Spring AOP使用了两种代理机制:一种是基于JDK的动态代理,一种是基于CGLib的动态代理.①JDK动态代理:使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚第二个入参 interfaces就是需要代理实例实现的接口列表.②CGLib:采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑.③对比:CGLib所创建的动态代理对象的性能比JDK的高大概10倍,但CGLib在创建代理对象的时间比JDK大概多8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需重复的创建代理对象,所以比较适合CGLib动态代理技术,反之选择JDK代理。值得一提的是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中final的方法进行代理。但是这种实现方式存在三个明显需要改进的地方:a.目标类的所有方法都添加了横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定的方法添加横切逻辑;b.我们通过硬编码的方式制定了织入横切逻辑的织入点,即在目标业务方法的开始和结束前织入代码;c.我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用;还有一个问题是:spring依赖注入时,什么时候会创建代理类,有时候是cglib有时候是jdkproxy有时候只是普通实例,有兴趣的可以查阅资料,getBean依赖注入过程,可查看IOC源码。下面我们举个例子看看aop事务注解是怎么实现的。 JDK动态代理:aop中生成的代理类是JdkDynamicAopProxy子类,debug调试的时候可以看到,打开源码可看到实现了AopProxy和invocationHandler也就实现invoke方法。 invoke关键代码:// Get the interception chain for this method.加载一系列的拦截器List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);TransactionInterceptor是事务拦截器,所有带有@Transactional注解的方法都会经过拦截器invoke方法拦截,点进方法里面可以发现代码如下:比如回滚方法点进去发现是获取事务管理器然后回滚

March 18, 2019 · 1 min · jiezi

Golang 函数执行时间统计装饰器的一个实现

背景最近在搭一个新项目的架子,在生产环境中,为了能实时的监控程序的运行状态,少不了逻辑执行时间长度的统计。时间统计这个功能实现的期望有下面几点:实现细节要剥离:时间统计实现的细节不期望在显式的写在主逻辑中。因为主逻辑中的其他逻辑和时间统计的抽象层次不在同一个层级用于时间统计的代码可复用统计出来的时间结果是可被处理的。对并发编程友好实现思路统计细节的剥离最朴素的时间统计的实现,可能是下面这个样子:func f() { startTime := time.Now() logicStepOne() logicStepTwo() endTime := time.Now() timeDiff := timeDiff(startTime, endTime) log.Info(“time diff: %s”, timeDiff)}《代码整洁之道》告诉我们:一个函数里面的所有函数调用都应该处于同一个抽象层级。在这里时间开始、结束的获取,使用时间的求差,属于时间统计的细节,首先他不属于主流程必要的一步,其次他们使用的函数 time.Now() 和 logicStepOne, logicStepTwo 并不在同一个抽象层级。因此比较好的做法应该是把时间统计放在函数 f 的上层,比如:func doFWithTimeRecord() { startTime: = time.Now() f() endTime := Time.Now() timeDiff := timeDIff(startTime, endTime) log.Info(“time diff: %s”, timeDiff)}时间统计代码可复用&统计结果可被处理&不影响原函数的使用方式我们虽然达成了函数内抽象层级相同的目标,但是大家肯定也能感受到:这个函数并不好用。原因在于,我们把要调用的函数 f 写死在了 doFWithTimeRecord 函数中。这意味着,每一个要统计时间的函数,我都需要实现一个 doXXWithTimeRecord, 而这些函数里面的逻辑是相同的,这就违反了我们 DRY(Don’t Repeat Yourself)原则。因此为了实现逻辑的复用,我认为装饰器是比较好的实现方式:将要执行的函数作为参数传入到时间统计函数中。举个网上看到的例子实现一个功能,第一反应肯定是查找同行有没有现成的轮子。不过看了下,没有达到自己的期望,举个例子:type SumFunc func(int64, int64) int64func timedSumFunc(f SumFunc) SumFunc { return func(start, end int64) int64 { defer func(t time.Time) { fmt.Printf("— Time Elapsed: %v —\n", time.Since(t)) }(time.Now()) return f(start, end) }}说说这段代码不好的地方:这个装饰器入参写死了函数的类型:type SumFunc func(int64, int64) int64也就是说,只要换一个函数,这个装饰器就不能用了,这不符合我们的第2点要求这里时间统计结果直接打印到了标准输出,也就是说这个结果是不能被原函数的调用方去使用的:因为只有掉用方,才知道这个结果符不符合预期,是花太多时间了,还是正常现象。这不符合我们的第3点要求。怎么解决这两个问题呢?这个时候,《重构,改善既有代码的设计》告诉我们:Replace Method with Method Obejct——以函数对象取代函数。他的意思是当一个函数有比较复杂的临时变量时,我们可以考虑将函数封装成一个类。这样我们的函数就统一成了 0 个参数。(当然,原本就是作为一个 struct 里面的方法的话就适当做调整就好了)现在,我们的代码变成了这样:type TimeRecorder interface { SetCost(time.Duration) TimeCost() time.Duration}func TimeCostDecorator(rec TimeRecorder, f func()) func() { return func() { startTime := time.Now() f() endTime := time.Now() timeCost := endTime.Sub(startTime) rec.SetCost(timeCost) }}这里入参写成是一个 interface ,目的是允许各种函数对象入参,只需要实现了 SetCost 和 TimeCost 方法即可对并发编程友好最后需要考虑的一个问题,很多时候,一个类在整个程序的生命周期是一个单例,这样在 SetCost 的时候,就需要考虑并发写的问题。这里考虑一下几种解决方案:使用装饰器配套的时间统计存储对象,实现如下:func NewTimeRecorder() TimeRecorder { return &timeRecorder{}}type timeRecorder struct { cost time.Duration}func (tr *timeRecorder) SetCost(cost time.Duration) { tr.cost = cost}func (tr *timeRecorder) Cost() time.Duration { return tr.cost}抽离出存粹的执行完就可以销毁的函数对象,每次要操作的时候都 new 一下函数对象内部对 SetCost 函数实现锁机制这三个方案是按推荐指数从高到低排序的,因为我个人认为:资源允许的情况下,尽量保持对象不可变;同时怎么统计、存储使用时长其实是统计时间模块自己的事情。单元测试最后补上单元测试:func TestTimeCostDecorator(t *testing.T) { testFunc := func() { time.Sleep(time.Duration(1) * time.Second) } type args struct { rec TimeRecorder f func() } tests := []struct { name string args args }{ { “test time cost decorator”, args{ NewTimeRecorder(), testFunc, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := TimeCostDecorator(tt.args.rec, tt.args.f) got() if tt.args.rec.Cost().Round(time.Second) != time.Duration(1) * time.Second.Round(time.Second) { “Record time cost abnormal, recorded cost: %s, real cost: %s”, tt.args.rec.Cost().String(), tt.Duration(1) * time.Second, } }) }}测试通过,验证了时间统计是没问题的。至此,这个时间统计装饰器就介绍完了。如果这个实现有什么问题,或者大家有更好的实现方式,欢迎大家批评指正与提出~原文地址:https://blog.coordinate35.cn/… ...

March 18, 2019 · 2 min · jiezi

设计模式的六大原则和三种类型

最近在看设计模式相关的知识,在此记录并分享之。 设计模式总共23种。有六大原则和三种类型。六大原则 1.单一职责原则—Single Responsibility Principle定义: 就一个类而言,应该仅有一个引起它变化的原因。说明: 如果一个类承担的职责过多,就等于把这些职责耦合在一起。当业务越来越复杂时,改动一个地方就会引起整个函数的大改动。别人看你的代码也会理解起来很费解。当你把每一个函数的功能拆分的非常细致的话,后期维护修改或者他们阅读你的代码都会很轻松。2.里式替换原则—LiskovSubstitution Principle定义: 子类型必须能够替换掉他们的父类型。说明: 如果把一个父类对象替换成一个子类对象,程序行为完全没有变化,只有这样,父类才能真正被复用,子类能够在父类的基础上增加新的行为。 里式替换原则是对开-闭原则的补充3.依赖倒置原则—DependenceInversion Principle定义: A.高层模块不应该依赖低层模块。两个都应该依赖抽象; B.抽象不应该依赖细节,细节应该依赖抽象;(针对接口编程,而不是针对实现)说明 抛弃面向过程开发,减少各个模块的耦合性,提高复用性4.合成聚合原则—Composite/Aggregate Reuse Principle定义: 尽量使用合成/聚合,尽量不要使用类继承说明 如果子类和父类有较强的耦合依赖关系,则父类的任何改变都会导致子类发生改变。复用子类时有需求而必须改变父类才能实现,这种情况应当避免。5.迪米特法则—Law Of Demeter定义: 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用;说明 类之间的设计要松耦合,耦合性越低,复用性越高。6.开放-封闭原则—Open Closed Principle定义: 软件实体(类,模块,函数等等)应该可以扩展,但是不可以修改;说明 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。 这样的设计,能够面对需求改变却可以保持相对稳定,从而使系统在第一个版本以后不断推出新的版本;面对需求,对程序的改动是通过增加新的代码进行的,而不是更改现有的代码; 开放封闭原则,是最为重要的设计原则,Liskov替换原则和合成/聚合复用原则为开放封闭原则的实现提供保证。三种类型创建型、结构型、行为型。1.创建型创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:工厂方法模式(Factory Method Pattern)抽象工厂模式(Abstract Factory Pattern)建造者模式(Builder Pattern)原型模式(Prototype Pattern)单例模式(Singleton Pattern)2.结构型结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:适配器模式(Adapter Pattern)桥接模式(Bridge Pattern)组合模式(Composite Pattern)装饰者模式(Decorator Pattern)外观模式(Facade Pattern)享元模式(Flyweight Pattern)代理模式(Proxy Pattern)3.行为型行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:责任链模式(Chain of Responsibility Pattern)命令模式(Command Pattern)解释器模式(Interpreter Pattern)迭代器模式(Iterator Pattern)中介者模式(Mediator Pattern)备忘录模式(Memento Pattern)观察者模式(Observer Pattern)状态模式(State Pattern)策略模式(Strategy Pattern)模板方法模式(Template Method Pattern)访问者模式(Visitor Pattern)模式虽多,JavaScript中常用的有14种模式单例模式策略模式代理模式迭代器模式观察者模式(发布-订阅模式)命令模式组合模式模板方法模式享元模式职责链模式中介者模式装饰器模式状态模式适配器模式

March 15, 2019 · 1 min · jiezi

PHP面试常考之设计模式——策略模式

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.11至3.15)的一三五更新的文章如下:周一:PHP面试常考之设计模式——工厂模式周三:PHP面试常考之设计模式——建造者模式周五:PHP面试常考之设计模式——策略模式自己上传了一本电子书“5种原则和23种设计模式”到百度云,关注公众号:“琉忆编程库”,回复:“23”,我发给你。以下内容如需转载,请注明作者和出处。策略模式介绍策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。UML图说明抽象策略角色: 策略类,通常由一个接口或者抽象类实现。具体策略角色:包装了相关的算法和行为。环境角色:持有一个策略类的引用,最终给客户端调用。应用场景1、 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。2、 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。3、 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。使用策略模式的好处1、 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。2、 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。3、 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。实现代码<?phpheader(“Content-type:text/html;Charset=utf-8”);//抽象策略接口abstract class Strategy{ abstract function wayToSchool();}//具体策略角色class BikeStrategy extends Strategy{ function wayToSchool(){ echo “骑自行车去上学”; }}class BusStrategy extends Strategy{ function wayToSchool(){ echo “乘公共汽车去上学”; }}class TaxiStrategy extends Strategy{ function wayToSchool(){ echo “骑出租车去上学”; }}//环境角色class Context{ private $strategy; //获取具体策略 function getStrategy($strategyName){ try{ $strategyReflection = new ReflectionClass($strategyName); $this->strategy = $strategyReflection->newInstance(); }catch(ReflectionException $e){ $this->strategy = “”; } } function goToSchool(){ $this->strategy->wayToSchool(); // var_dump($this->strategy); }}//测试$context = new Context();$context->getStrategy(“BusStrategy”);$context->goToSchool(); ?>自己上传了一本电子书“5种原则和23种设计模式”到百度云,关注公众号:“琉忆编程库”,回复:“23”,我发给你。自己编写的《PHP程序员面试笔试宝典》和《PHP程序员面试笔试真题解析》书籍,已在各大电商平台销售。书籍在手,Offer我有。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

March 15, 2019 · 1 min · jiezi

吃透动态代理,解密spring AOP源码(三)

上节讲到动态代理生成的类为$Proxy0,但是在我们项目里面却不存在,实际我们是用了这个实现类调用了方法,想要知道这个问题,首先要理解类的完整生命周期.Java源文件:即我们在IDE里面写的.java文件Java字节码:即编译器编译之后的.class文件(javac命令).备注:Java代码为何能够跨平台,和Java字节码技术是分不开的,这个字节码在windows,在linux下都是可以运行的class对象:工程启动的时候classLoader类加载器会扫描这些字节码并加载到classLoader上面生成class对象,有了类对象,便可以new实例了。(class对象保存在方法区元空间JDK1.8)卸载:垃圾回收,关于回收机制,算法有兴趣可以去了解。class对象什么时候被回收?答:可达性分析,当发现某个类不被引用,类会被回收类的生命周期与动态代理关系 动态代理是没有Java源文件,直接生成Java字节码的,加载到JVM上面的。字节码来源于内存,比如tomcat的热加载就是从网络传输过来的。 既然是直接生成的Java字节码,是怎么生成的?从源码开始分析,从Proxy.newProxyInstance方法开始看。Class<?> cl = getProxyClass0(loader, intfs);这行代码生成了.class字节码并且生成了class对象,然后拿这个类对象获取构造函数,再newInstance,生成实例对象,是通过反射的机制。重点还是怎么生成.class字节码。接下来apply()方法往下看生成了字节码数组,从而生成了Java字节码,defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)则是加载字节码文件,此方法为native方法,C语言方法,操作系统类库(C/C++/汇编)。字节码文件的结构是如何的呢?我们把class文件生成出来并在反编译工具打开,这里就用到了源码里面的方法了。生成.class文件的代码如下:public static void generateClass(String proxyName, Class[] paramArrayOfClass, Class clazz) throws IOException { byte[] classFile=ProxyGenerator.generateProxyClass( proxyName, paramArrayOfClass); String path=clazz.getResource(".").getPath(); System.out.println(path); FileOutputStream outputStream =null; outputStream = new FileOutputStream(path + proxyName + “p.class”); outputStream.write(classFile); outputStream.flush(); outputStream.close(); } 在刚刚的动态代理测试类增加几行代码: public static void main(String[] args) throws IOException { // 代购公司C,负责代购所有产品 DynamicProxyCompanyC proxy = new DynamicProxyCompanyC(); // 日本有家A公司生产男性用品 ManToolFactory dogToolFactory = new AManFactory(); // 代购A公司的产品 proxy.setFactory(dogToolFactory); // 创建A公司的代理对象 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance(); // 代理对象完成代购男性用品 proxyObject.saleManTool(“D”); System.out.println("————–"); // 日本有家B公司生产女性用品 WomanToolFactory womanToolFactory = new BWomanFactory(); // 代购B公司的产品 proxy.setFactory(womanToolFactory); // 创建B公司的代理对象 WomanToolFactory proxyObject1 = (WomanToolFactory) proxy.getProxyInstance(); // 代理对象完成代购女性用品 proxyObject1.saleWomanTool(1.8); //生成代理类的.class文件 DynamicProxyCompanyC.generateClass(proxyObject1.getClass().getSimpleName(), womanToolFactory.getClass().getInterfaces(), womanToolFactory.getClass()); } 根据打印出来的class文件路径打开并在反编译工具上打开动态代理生成的类就是这个了,调用业务方法saleWomanTool实际上变成了这个h.invoke,而这个h是所有Proxy类里面含有的 protected InvocationHandler h;(源码可见),在用Proxy创建代理实例的时候已经传入过了。所以调用方法saleWomanTool就有了前置增强和后置增强。到这里已经解开了动态代理的原理 ...

March 14, 2019 · 1 min · jiezi

行为型模式:状态模式

十一大行为型模式之八:状态模式。简介姓名 :状态模式英文名 :State Pattern价值观 :有啥事让状态我来维护个人介绍 :Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。(来自《设计模式之禅》)你要的故事现在有好多个人贷款软件,比如:支付宝、360借条(打广告。。。)等等。贷款会有一个用户状态流程,游客->注册用户->授信用户->借款用户(这里简化了状态,只用 4 个)。每个状态拥有的权限不一样,如下图所示。从上图可以看到,一个用户有 3 种行为,分别是注册、授信、借款。当注册成功后,用户的状态就从『游客』改变为『注册用户』;当授信成功后,用户的状态就从『注册用户』改变为『授信用户』;当借款成功后,用户的状态就从『授信用户』改变为『借款用户』。现在我们就来实现用户注册、授信、借款的过程,因为每个状态的权限不一样,所以这里需要根据用户的状态来限制用户行为。很快,我们就完成下面的代码。class User { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } public void register() { if (“none”.equals(state)) { System.out.println(“游客。注册中。。。”); }else if (“register”.equals(state)) { System.out.println(“注册用户。不需要再注册。”); } else if (“apply”.equals(state)) { System.out.println(“授信用户。不需要再注册。”); } else if (“draw”.equals(state)) { System.out.println(“借款用户。不需要再注册。”); } } public void apply() { if (“none”.equals(state)) { System.out.println(“游客。不能申请授信。”); }else if (“register”.equals(state)) { System.out.println(“注册用户。授信申请中。。。”); } else if (“apply”.equals(state)) { System.out.println(“授信用户。不需要再授信。”); } else if (“draw”.equals(state)) { System.out.println(“借款用户。不需要再授信。”); } } public void draw(double money) { if (“none”.equals(state)) { System.out.println(“游客。申请借款【” + money + “】元。不能申请借款。”); } else if (“register”.equals(state)) { System.out.println(“注册用户。申请借款【” + money + “】元。还没授信,不能借款。”); } else if (“apply”.equals(state)) { System.out.println(“授信用户。申请借款【” + money + “】元。申请借款中。。。”); } else if (“draw”.equals(state)) { System.out.println(“授信用户。申请借款【” + money + “】元。申请借款中。。。”); } }}public class NoStateTest { public static void main(String[] args) { User user = new User(); user.setState(“register”); user.draw(1000); }}打印结果:注册用户。申请借款【1000.0】元。还没授信,不能借款。上面代码实现了用户 register (注册),apply (授信),draw (借款) 这 3 种行为,每个行为都会根据状态 state 来做权限控制。看起来有点繁琐,扩展性不高,假设新增了一个状态,那么注册、授信、借款这 3 种行为的代码都要修改。下面通过状态模式来解决这个问题。我们把状态给抽出来,作为一个接口,因为在每种状态中都可能有注册、授信、借款行为,所以把这 3 个行为作为状态接口的方法,让每个状态子类都实现相应的行为控制。如下代码所示。interface State { void register(); void apply(); void draw(double money);}/** * 游客 /class NoneState implements State { @Override public void register() { System.out.println(“游客。注册中。。。”); } @Override public void apply() { System.out.println(“游客。不能申请授信。”); } @Override public void draw(double money) { System.out.println(“游客。申请借款【” + money + “】元。不能申请借款。”); }}/* * 注册状态 /class RegisterState implements State { @Override public void register() { System.out.println(“注册用户。不需要再注册。”); } @Override public void apply() { System.out.println(“注册用户。授信申请中。。。”); } @Override public void draw(double money) { System.out.println(“注册用户。申请借款【” + money + “】元。还没授信,不能借款。”); }}/* * 授信状态 /class ApplyState implements State { @Override public void register() { System.out.println(“授信用户。不需要再注册。”); } @Override public void apply() { System.out.println(“授信用户。不需要再授信。”); } @Override public void draw(double money) { System.out.println(“授信用户。申请借款【” + money + “】元。申请借款中。。。”); }}/* * 借款状态 */class DrawState implements State { @Override public void register() { System.out.println(“借款用户。不需要再注册。”); } @Override public void apply() { System.out.println(“借款用户。不需要再授信。”); } @Override public void draw(double money) { System.out.println(“申请借款【” + money + “】元。申请借款中。。。”); }}class User1 { private State state; public State getState() { return state; } public void setState(State state) { this.state = state; } public void register() { this.state.register(); } public void apply() { this.state.apply(); } public void draw(double money) { this.state.draw(money); }}public class StateTest { public static void main(String[] args) { User1 user1 = new User1(); user1.setState(new RegisterState()); user1.apply(); user1.draw(1000); user1.setState(new ApplyState()); user1.draw(2000); }}打印结果:注册用户。授信申请中。。。注册用户。申请借款【1000.0】元。还没授信,不能借款。授信用户。申请借款【2000.0】元。申请借款中。。。看上面代码,我们抽象了 State 接口,4 种状态分别用 NoneState (游客)、RegisterState (注册)、ApplyState (授信)、DrawState (借款) 表示。而每个状态都有 3 种行为,它们各自对这些行为进行权限控制。这样子实现可以让权限逻辑分离开,分散到每个状态里面去,如果以后要业务扩展,要新增状态,那就很方便了,只需要再实现一个状态类就可以,不会影响到其他代码。这也是为什么《阿里巴巴 Java 开发手册》里面讲的,当超过 3 层的 if-else 的逻辑判断代码,推荐用状态模式来重构代码。总结状态模式 很好的减低了代码的复杂性,从而提高了系统的可维护性。在业务开发中可以尝试使用,比如在迭代开发中,业务逻辑越来越复杂,从而不得不使用很多 if-else 语句来实现时,就可以考虑一下是不是可以用 状态模式 来重构,特别是一些有状态流程转换方面的业务。看到这篇文章,想想工作中是不是有些复杂的代码可以重构,赶紧行动起来。推荐阅读:行为型模式:观察者模式行为型模式:迭代器模式行为型模式:策略模式设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。 ...

March 14, 2019 · 2 min · jiezi

吃透动态代理,解密spring AOP源码(二)

紧接着上节,为了解决静态代理的问题,出现了动态代理,先上代码:/** * 动态代理 */public class DynamicProxyCompanyC implements InvocationHandler { // 被代理的对象,即真实对象 private Object factory; public Object getFactory() { return factory; } public void setFactory(Object factory) { this.factory = factory; } // 通过proxy获取动态代理的对象 public Object getProxyInstance() { //第三个参数是InvocationHandler,传入自身说明此proxy对象是和自身的invoke方法合作的,代理对象方法调用会经过下面invoke的增强 return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), this); } @Override /**通过动态代理对象对方法进行增强 * @param proxy 代理对象 * @param method 要增强的方法(拦截的方法) * @param args 方法参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { dosomeThingBefore(); Object ret = method.invoke(factory, args);// 通过反射机制调用方法 dosomeThingAfter(); return ret; } public void dosomeThingBefore() { System.out.println(“售前服务,负责产品调研,兴趣爱好”); } public void dosomeThingAfter() { System.out.println(“售后服务,包装丶送货上门一条龙服务”); }}假设动态代理是一个代购公司,私有变量Object factory为动态生成的具体的真实对象,可代购对应的产品 测试类:public class Proxytest { public static void main(String[] args) { // 代购公司C,负责代购所有产品 DynamicProxyCompanyC proxy = new DynamicProxyCompanyC(); // 日本有家A公司生产男性用品 ManToolFactory dogToolFactory = new AManFactory(); // 代购A公司的产品 proxy.setFactory(dogToolFactory); // 创建A公司的代理对象 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance(); // 代理对象完成代购男性用品 proxyObject.saleManTool(“D”); System.out.println("————–"); // 日本有家B公司生产女性用品 WomanToolFactory womanToolFactory = new BWomanFactory(); // 代购B公司的产品 proxy.setFactory(womanToolFactory); // 创建B公司的代理对象 WomanToolFactory proxyObject1 = (WomanToolFactory) proxy.getProxyInstance(); // 代理对象完成代购女性用品 proxyObject1.saleWomanTool(1.8); }}// 售前服务,负责产品调研,兴趣爱好// A工厂出售男性用品,D罩杯// 售后服务,包装丶送货上门一条龙服务// ————–// 售前服务,负责产品调研,兴趣爱好// B工厂生产女性用品,长度1.8米// 售后服务,包装丶送货上门一条龙服务 动态代理解决了上节说的开闭原则,那么接下来我们要解密动态代理的原理,重点类DynamicProxyCompanyC :1.实现了InvocationHandler接口;2.通过proxy获取动态代理的对象。根据我们此例子里面来说,动态代理就类似一个代购公司,可代购所有产品,需要购买哪个产品的时候就实例化一个真实对象(如测试类需要男性用品则将接口引用指向真实对象AManFactory),根据真实对象创建代理对象来执行具体的方法,图解如下:Proxy:接下来我们先初步看一下JDK里面的Proxy这个源码这个注释是说Proxy提供个一个静态方法来创建代理类和代理实例,它也是所有由此方法创建的代理类的父类。静态方法创建代理实例即方法newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h);InvocationHandler :InvocationHandler 是一个接口,定义了invoke(Object proxy, Method method, Object[] args)方法总的来说Proxy专门负责new一个实例(真实对象),而具体方法做什么,业务怎样增强就由InvocationHandler(抽象对象)的invoke方法(抽象对象即接口定义的方法)来决定。接下来我们要搞清楚动态代理的底层原理,首先我们调试一下,会发现 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance()中创建的proxyObject 对象类名是&Proxy0,是ManToolFactory接口的实现类。但是我们项目工程里面却没有&Proxy0这个类,那它究竟是怎么出现的,下节讲解。 ...

March 13, 2019 · 1 min · jiezi

设计模式-单例模式详解

单例模式 保证一个类在任何情况下都绝对只有一个实例,并且提供一个全局访问点 需要隐藏其所有构造方法 优点: 在内存中只有一个实例,减少了内存开销 可以避免对资源的多重占用 设置全局访问点,严格控制访问 缺点: 没有接口,扩展困难 如果要扩展单例对象,只有修改代码,没有别的途径应用场景 ServletContext ServletConfig ApplicationContext DBPool常见的单例模式写法饿汉式单例 饿汉式就是在初始化的时候就初始化实例 两种代码写法如下:public class HungrySingleton { private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); private HungrySingleton() { } private static HungrySingleton getInstance() { return HUNGRY_SINGLETON; }}public class HungryStaticSingleton { private static final HungryStaticSingleton HUNGRY_SINGLETON; static { HUNGRY_SINGLETON = new HungryStaticSingleton(); } private HungryStaticSingleton() { } private static HungryStaticSingleton getInstance() { return HUNGRY_SINGLETON; }} 如果没有使用到这个对象,因为一开始就会初始化实例,这种方式会浪费内存空间懒汉式单例 懒汉式单例为了解决上述问题,则是在用户使用的时候才初始化单例public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { lazySimpleSingleton = new LazySimpleSingleton();//11行 } return lazySimpleSingleton; }} 上述方式,线程不安全,如果两个线程同时进入11行,那么会创建两个对象,需要如下,给方法加锁public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public synchronized static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { lazySimpleSingleton = new LazySimpleSingleton(); } return lazySimpleSingleton; }} 上述方式虽然解决了线程安全问题,但是整个方法都是锁定的,性能比较差,所以我们使用方法内加锁的方式解决提高性能public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) {//11行 lazySimpleSingleton = new LazySimpleSingleton(); } } return lazySimpleSingleton; }} 上述方式如果两个线程同时进入了11行,一个线程a持有锁,一个线程b等待,当持有锁的a线程释放锁之后到return的时候,第二个线程b进入了11行内部,创建了一个新的对象,那么这时候创建了两个线程,对象也并不是单例的。所以我们需要在12行位置增加一个对象判空的操作。public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) { if (lazySimpleSingleton != null) { lazySimpleSingleton = new LazySimpleSingleton(); } } } return lazySimpleSingleton; }} 上述方式还是有风险的,因为CPU执行时候会转化成JVM指令执行: 1.分配内存给对象 2.初始化对象 3.将初始化好的对象和内存地址建立关联,赋值 4.用户初次访问 这种方式,在cpu中3步和4步有可能进行指令重排序。有可能用户获取的对象是空的。那么我们可以使用volatile关键字,作为内存屏障,保证对象的可见性来保证我们对象的单一。public class LazySimpleSingleton { private static volatile LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { //加上空判断保证初只会初始化一次 if (lazySimpleSingleton == null) { synchronized (LazySimpleSingleton.class) { if (lazySimpleSingleton != null) { lazySimpleSingleton = new LazySimpleSingleton(); } } } return lazySimpleSingleton; }}静态内部类单例 还有一种懒汉式单例,利用静态内部类在调用的时候等到外部方法调用时才执行,巧妙的利用了内部类的特性,jvm底层逻辑来完美的避免了线程安全问题public class LazyInnerClassSingleton { private LazyInnerClassSingleton() { } public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); }} 这种方式虽然能够完美单例,但是我们如果使用反射的方式如下所示,则会破坏单例public class LazyInnerClassTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = LazyInnerClassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Object o1 = c.newInstance(); Object o2 = LazyInnerClassSingleton.getInstance(); System.out.println(o1 == o2); }} 怎么办呢,我们需要一种方式控制访问者的行为,通过异常的方式去限制使用者的行为,如下所示public class LazyInnerClassSingleton { private LazyInnerClassSingleton() { throw new RuntimeException(“不允许构建多个实例”); } public static final LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); }} 还有一种方式会破坏单例,那就是序列化破坏我们的单例,如下所示序列化破坏单例 我们写一个序列化的方法来尝试一下上述写法是否是满足序列化的。public class SeriableSingletonTest { public static void main(String[] args) { SeriableSingleton seriableSingleton = SeriableSingleton.getInstance(); SeriableSingleton s2; FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream(“d.o”); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(seriableSingleton); oos.flush(); oos.close(); fis = new FileInputStream(“d.o”); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (SeriableSingleton) ois.readObject(); ois.close(); System.out.println(seriableSingleton); System.out.println(s2); System.out.println(s2 == seriableSingleton); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }} 为什么序列化会破坏单例呢,我们查看ObjectInputStream的源码首先,我们查看ObjectInputStream的readObject方法查看readObject0方法查看checkResolve(readOrdinaryObject(unshared)方法可以看到 红框内三目运算符内如果desc.isInstantiable()为真就创建新对象,不为空就返回空,此时我们查看desc.isInstantiable()方法此处cons是如果有构造方法就会返回true,当然我们一个类必然会有构造方法的,所以这就是为什么序列化会破坏我们的单例那么怎么办呢,我们只需要重写readResolve方法就行了public class SeriableSingleton implements Serializable { private SeriableSingleton() { throw new RuntimeException(“不允许构建多个实例”); } public static final SeriableSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final SeriableSingleton LAZY = new SeriableSingleton(); } private Object readResolve() { return getInstance(); }}为什么重写这个readResolve 的方法就能够避免序列化破坏单例呢回到上述readOrdinaryObject方法,可以看到有一个hasReadResolveMethod方法点进去可以看到 readResolveMethod在此处赋值也就是我们如果类当中有此方法则在hasReadResolveMethod当中返回的是true那么会进入readOrdinaryObject的如下部分并且如下所示,调用我们的readResolve方法获取对象,来保证我们对象是单例的 但是重写readResolve方法,只不过是覆盖了反序列化出来的对象,但是还是创建了两次,发生在JVM层面,相对来说比较安全,之前反序列化出来的对象会被GC回收注册式单例枚举单例 枚举式单例属于注册式单例,他把每一个实例都缓存到统一的容器中,使用唯一标识获取实例。也是比较推荐的一种写法,如下所示:public enum EnumSingleton { INSTANCE; private Object data; public static EnumSingleton getInstance() { return INSTANCE; }} 反编译上述文件,可以看到 那么序列化能不能破坏枚举呢 在ObjectInputStream的readObject方法中有针对枚举的判断上述通过一个类名和枚举名字值来确定一个枚举值。从而枚举在序列化上是不会破坏单例的。我们尝试使用反射来创建一个枚举对象public enum EnumSingleton { INSTANCE; private Object data; EnumSingleton() { } public static EnumSingleton getInstance() { return INSTANCE; } public static void main(String[] args) { Class clazz = EnumSingleton.class; try { Constructor c = clazz.getDeclaredConstructor(String.class, int.class); c.newInstance(“dd”, 1); } catch (Exception e) { e.printStackTrace(); } }}抛出异常查看Constructor源码可以看到可以看到jdk层面如果判断是枚举会抛出异常,所以枚举式单例是一种比较推荐的单例的写法。容器式单例这种方式是通过容器的方式来保证我们对象的单例,常见于Spring的IOC容器public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance();//12 ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } return ioc.get(className); } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); final CountDownLatch countDownLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { executorService.submit(new Runnable() { @Override public void run() { Object o = ContainerSingleton.getBean(“com.zzjson.singleton.register.ContainerSingleton”); System.out.println(o + “”); countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); }}这种方式测试可见出现了几次不同对象的情况因为我们线程在12行可能同时进入,这时候我们需要加一个同步锁如下,这样创建对象才是只会创建一个的public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } } return ioc.get(className); } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(100); final CountDownLatch countDownLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { executorService.submit(new Runnable() { @Override public void run() { Object o = ContainerSingleton.getBean(“com.zzjson.singleton.register.ContainerSingleton”); System.out.println(o + “”); countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); }}ThreadLocal单例这种方式只能够保证在当前线程内的对象是单一的public class ThreadLocalSingleton { private ThreadLocalSingleton() { } private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private static ThreadLocalSingleton getInstance() { return threadLocalInstance.get(); } }文中源码地址设计模式 ...

March 13, 2019 · 4 min · jiezi

说说PHP框架的MVC架构

前言在说 MVC 架构之前,先说说PHP框架吧。很多很多学完PHP语言的人,面对的就是PHP各种各样的框架。什么TP啊、Yii啊、CI啊,还有很流行的laravel啊等等。他们的大部分都会说自己是基于 MVC 的架构,接着你得试着去理解 MVC 的逻辑,并尝试着用这样的逻辑去构建一个网站,然后会说 MVC 真香~面试很多 PHP 的面试中,可能会问关于 MVC 的问题,比如 MVC 到底是什么意思,怎样理解这种架构。然而很多人的理解是 model 是模型,他对应着数据库中的表结构;view 对应着页面,用于展示;controller 主要用来写各种逻辑,关联数据和页面的显示。以上回答基本上没有问题,但一个网站的结构真的有那么简单么?显然不是设计在说之前,首先让我们了解一下设计模式的一种:中介者模式。一个形象的理解就是:港行插头和国行插头的转接头。在 MVC 架构中 controller 就是这个转接头。它只负责把 model 中的数据转接给 view,对于访问者来说,他们是看不到 model 中保存的真实数据的。从另外一个角度来说,这种中介者模式可以很好的将两层数据进行友好的通信。爬坑这种模式真的那么好么?随着业务逻辑的越来越复杂,会发现 controller 中的代码越来越多,甚至自己都不愿去调整和优化冗余代码。但从宏观上来说,网站无非是请求多一些,表单多一些,页面多一些啊,其他也没什么了,为什么会这样呢?没错,就是因为这样或那样的东西比较多,导致 controller 中每个方法都很长,那么能想到的解决方法就是拆分。如果用过 yii 框架,那么你会知道最简单的办法是加一个请求form层,代码如下:class AuthController { public function login() { $FLogin = new loginForm(); $FLogin->save(); }}// 一般在独立的文件夹中class loginForm { public function __construct() { $post = $_POST; } public function save() { }}以上的就是解决 controller 中 form 表单的问题,这个问题基本上能缓解很多代码问题。发散从解决 form 层来看,其实有很多类似的问题都能解决。我们知道前端有个叫做 vue.js 的框架,它里面提到一个概念叫做 MVVM 模型。其实在展现复杂页面的时候,后端在对外输出数据时,完全也可以采用这玩意进行数据输出。至于如何建立这样的一个模型,那就具体得看业务逻辑了。这里简单拿用户中心举个例子,因为往往这里不仅仅需要一个表的数据:class AuthController { public function userCenterAction() { return new userVM(); }}class userVM { public $user; public $orders; public $other; public function __construct() { $this->user = $this->getUser(); $this->orders = $this->getOrders(); $this->handle(); } private function getUser() { return NULL; } private function getOrders() { return NULL; } private function handle() { }}以上代码中,有个 VM 层,可以将相关获取数据的代码放在各自的方法中,然后在 handle 方法中自由组合。这样在 controller 中的代码也非常便于管理。再想想,有没有可以封装的其他层呢?其实是有的,比如 request 层,还有经常被框架封装好的 validate 层,还有 laravel 中比较流行的 Middleware 层等等。只能说系统越复杂,层越多。每个复杂系统的背后都蕴含着高级开发工程师和架构师的设计思路。以上说那么多,不知道读者能否理解这些东西,就拿以上代码来说,里面就蕴含着另一种设计模式:建造者模式。总结代码写多了,也就知道其中蕴含的道理了。当一个新框架诞生后,关注点从学习这个框架,慢慢变成了这个框架是如何设计的,解决什么样的问题。哪些地方用了比较好的技术和方法,从中能收获到什么。一些地方的设计思路是什么样的,有么有更好的设计,为何我能想到,对方想不到呢,是不是我遗漏了什么。前几年使用过各种 PHP 框架,小到 CI,大到 Symfony。不用那么多框架,也体会不到这些东西。学习编程其实和英语一样,没什么捷径可以走。多写,多想,多练……以上 ...

March 13, 2019 · 1 min · jiezi

JS设计模式之Obeserver(观察者)模式、Publish/Subscribe(发布/订阅)模式

Obeserver(观察者)模式定义《js设计模式》中对Observer的定义:一个对象(称为subject)维持一系列依赖于它(观察者)的对象,将有关状态的任何变更自动通知给它们。《设计模式:可服用面向对象软件的基础》中对Observer的定义:一个或多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象上以便注册所感兴趣的内容。目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标感兴趣时,他们可以简单地将自己从中分离。下面看一个观察者模式的例子://观察者列表function ObserverList() { this.observerList = [];}ObserverList.prototype.add = function(obj) { return this.observerList.push(obj);};ObserverList.prototype.count = function() { return this.observerList.length;};ObserverList.prototype.get = function(index) { if(index > -1 && index < this.observerList.length) { return this.observerList[index ]; }};ObserverList.prototype.indexOf = function(obj, startIndex) { var i = startIndex; while(i < this.observerList.length) { if(this.observerList[i] === obj) { return i; } i++; } return -1;};ObserverList.prototype.removeAt = function(index) { this.observerList.splice(index, 1);};//目标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); }};//观察者function Observer(name, subject) { this.name = name; this.subject = subject; this.subscribe(this.subject);}Observer.prototype.update = function(context) { console.log(‘observer:’ + this.name + ’ content:’ + context);}Observer.prototype.subscribe = function(subject) { this.subject.addObserver(this);}var subject1 = new Subject();var subject2 = new Subject();var observer1 = new Observer(‘observer1’, subject1);var observer2 = new Observer(‘observer2’, subject1);var observer3 = new Observer(‘observer3’, subject2);subject1.notify(‘999感冒灵’);subject2.notify(‘999胃泰’);//observer:observer1 content:999感冒灵//observer:observer2 content:999感冒灵//observer:observer3 content:999胃泰常用的场景网页的事件绑定Publish/Subscribe(发布/订阅)模式下面我们来看一个发布订阅的具体实现;var pubsub={}; (function(q){ var topics={}, subUid=-1, subscribers, len; //发布广播事件,包含特定的topic名称和参数 q.publish=function(topic, args){ if(!topics[topic]){ return false; } subscribers=topics[topic]; len=subscribers ? subscribers.length : 0; while(len–){ subscribers[len].func(topic, args); } return this; }; q.subscribe=function(topic, func){ if(!topics[topic]){ topics[topic]=[]; } var token=(++subUid).toString(); topics[topic].push({ token:token, func:func }); return token; }; q.unsubscribe=function(token){ for(var m in topics){ if(topics[m]){ for(var i = 0, j=topics[m].length; i < j; i++){ if(topics[m][i].token === token){ topics[m].splice(i, 1); return token; } } } } return this; }; })(pubsub); function log1(topic ,data){ console.log(topic , data); } function log2(topic ,data){ console.log(“Topic is “+topic+” Data is “+data); } pubsub.subscribe(“sss”,log2); pubsub.subscribe(“sss”,log1); pubsub.subscribe(“cccc”,log2); pubsub.subscribe(‘aaa’, log1); pubsub.publish(“sss”,“aaaaa1”); pubsub.publish(“cccc”,“ssssss”); pubsub.publish(‘aaa’, ‘hahahahah’); //sss aaaaa1 //Topic is sss Data is aaaaa1 //Topic is cccc Data is ssssss //aaa hahahahah区别Observer模式要求希望接收到主题通知的观察者或对象必须订阅内容改变的事件。如图:Publish/Subscribe模式比较观察者模式则多了一个类似于话题调度中心的流程,发布者和订阅者解耦。观察者模式更像是去咖啡店点咖啡,向店家点一杯咖啡,然后做好后店家会送过来,我和店家是直接有交互的。发布订阅模式就像是我们微信里面订阅公众号,发布者把文章发布到这个公众号,我们订阅公众号后就会收到发布者发布的文章,我们可以关注和取消关注这个公众号。实际应用promisejquery的Callback内部方法感兴趣的小伙伴可以移步自己动手实现一个PromiseJS设计模式系列文章JS设计模式之Obeserver(观察者)模式、Publish/Subscribe(发布/订阅)模式JS设计模式之Factory(工厂)模式JS设计模式之Singleton(单例)模式JS设计模式之Facade(外观)模式JS设计模式之Module(模块)模式、Revealing Module(揭示模块)模式 ...

March 13, 2019 · 2 min · jiezi

PHP面试常考之设计模式——建造者模式

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.11至3.15)的一三五更新的文章如下:周一:PHP面试常考之设计模式——工厂模式周三:PHP面试常考之设计模式——建造者模式周五:PHP面试常考之设计模式——策略模式自己整理了一篇“什么是观察者模式?”的文章,关注公众号:“琉忆编程库”,回复:“观察”,我发给你。以下内容如需转载,请注明作者和出处。建造者模式介绍建造者模式又名生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。例如,一辆汽车由轮子,发动机以及其他零件组成,对于普通人而言,我们使用的只是一辆完整的车,这时,我们需要加入一个构造者,让他帮我们把这些组件按序组装成为一辆完整的车。UML图UML图说明Builder:抽象构造者类,为创建一个Product对象的各个部件指定抽象接口。ConcreteBuilder:具体构造者类,实现Builder的接口以构造和装配该产品的各个部件。定义并明确它所创建的表示。提供一个检索产品的接口Director:指挥者,构造一个使用Builder接口的对象。Product:表示被构造的复杂对象。ConcreateBuilder创建该产品的内部表示并定义它的装配过程。包含定义组成部件的类,包括将这些部件装配成最终产品的接口。实现示例<?php /*** chouxiang builer*/abstract class Builder{ protected $car; abstract public function buildPartA(); abstract public function buildPartB(); abstract public function buildPartC(); abstract public function getResult();}class CarBuilder extends Builder{ function __construct() { $this->car = new Car(); } public function buildPartA(){ $this->car->setPartA(‘发动机’); } public function buildPartB(){ $this->car->setPartB(‘轮子’); } public function buildPartC(){ $this->car->setPartC(‘其他零件’); } public function getResult(){ return $this->car; }}class Car{ protected $partA; protected $partB; protected $partC; public function setPartA($str){ $this->partA = $str; } public function setPartB($str){ $this->partB = $str; } public function setPartC($str){ $this->partC = $str; } public function show() { echo “这辆车由:”.$this->partA.’,’.$this->partB.’,和’.$this->partC.‘组成’; }}class Director{ public $myBuilder; public function startBuild() { $this->myBuilder->buildPartA(); $this->myBuilder->buildPartB(); $this->myBuilder->buildPartC(); return $this->myBuilder->getResult(); } public function setBuilder(Builder $builder) { $this->myBuilder = $builder; }}$carBuilder = new CarBuilder();$director = new Director();$director->setBuilder($carBuilder);$newCar = $director->startBuild();$newCar->show();?>自己整理了一篇“什么是观察者模式?”的文章,关注公众号:“琉忆编程库”,回复:“观察”,我发给你。自己编写的《PHP程序员面试笔试宝典》和《PHP程序员面试笔试真题解析》书籍,已在各大电商平台销售。书籍在手,offer我有。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。 ...

March 13, 2019 · 1 min · jiezi

吃透动态代理,解密spring AOP源码(一)

首先讲讲代理模式。什么是静态代理,为什么需要动态代理?代理模式:定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。目的:1.通过引入代理对象来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;2.通过代理对象对原有的业务增强。如图代理模式类图:简单看了下图解就开始上代码吧1.先定义一个抽象对象即公共接口类/** * 负责生产男性用品 /public interface ManToolFactory { public void saleManTool(String size);}2.真实对象 /* * A工厂负责生产男性用品 / public class AManFactory implements ManToolFactory { @Override public void saleManTool(String size) { System.out.println(“A工厂出售男性用品,大小为” + size ); }}3.代理对象 /* * 静态代理类 /public class StaticProxy implements ManToolFactory { // 代理的真实对象,多个的话考虑公用object也就是动态代理的实现 private AManFactory aManFactory;// 类似搬运工,代理真实对象的方法 public StaticProxy(AManFactory aManFactory) { this.aManFactory = aManFactory; } @Override public void saleManTool(String size) { dosomeThingBefore();//前置增强 aManFactory.saleManTool(size); dosomeThingAfter();//后置增强 } public void dosomeThingBefore() { System.out.println(“售前服务,负责产品的调研工作”); } public void dosomeThingAfter() { System.out.println(“售后服务,送门服务,三包等”); }}由代理模式可增强原有业务。问题来了,如今代理不仅仅帮忙代购男性用品,也要代购女性用品,那同样的我们就再定义一个接口。 /* * 负责生产女性用品的抽象对象 / public interface WomanToolFactory { public void saleWomanTool(Double length); } /* * B工厂专门负责生产男性用品(真实对象) */ public class BWomanFactory implements WomanToolFactory { @Override public void saleWomanTool(Double length) { System.out.println(“B工厂生产女性用品,长度” + length); } }此时代理类需要修改,开始思路如下public class StaticProxy implements ManToolFactory,WomanToolFactory { private AManFactory aManFactory; private BWomanFactory bManFactory; @Override public void saleManTool(String size) { //TODO } @Override public void saleWomanTool(Double length) { //TODO }那如果再多一个业务,代购点别的产品,那是不是又要再实现一个接口,这样就违背了设计模式的原则:开闭原则因此动态代理就出现了。动态代理看下节 ...

March 13, 2019 · 1 min · jiezi

简析IoC控制反转

简析IoC控制反转设计模式原则开闭原则:开放扩展,关闭修改。主要将的就是抽象化里氏代换替换原则:任何基类可以出现的地方,子类一定可以出现。依赖倒转原则:尽量依赖抽象接口编程而不要去依赖具体实例。????接口隔离原则:使用多个隔离的接口要好过单个接口。迪米特原则:一个实例尽量少知道与其他实例之间的相互作用关系,使功能模块相对独立。合成复用原则:尽量使用聚合和组合,少用依赖。????什么是IoC控制反转(简称IoC),是一种面向对象的设计思想,用来降低代码间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也就是将依赖被注入到要调用的对象中。控制反转一般和容器思想结合使用,Ioc就是将对象交给容器去控制,而不是在对象内部直接控制,主要是控制对象的内部依赖。谁控制谁IoC通过一个专门的容器来创建对象,通过IoC容器来控制对象控制什么控制实例的外部依赖关于反转正转:由对象去主动获取自己需要的依赖,并控制依赖叫正转反转:由new对象的容器去获取并创建依赖,对象被动接受依赖,从而组合成一个具有具体功能的实例。IoC的作用IoC是一种面向对象的变成思想和指导准则。解决传统控制正转开发中在类内部主动创建依赖类,从而导致类的内部耦合,难以复用。IoC把查找创建依赖的控制权反转给容器,由容器进行注入来组合创建一个对象,不同需求容器可以注入不同的对象,从而消除对象和依赖的耦合。IoC思想的实现DI依赖注入依赖注入指容器在运行中动态的将依赖注入到组件中,从而使组件具有具体的功能。通过依赖注入可以在特殊的地方指定同一个抽象方法去处理不同依赖对象的逻辑,而不需要了解和修改具体抽象方法的实现,只需要关注自己的业务层。谁依赖谁实例化类依赖控制容器为什么要依赖实例化类需要依赖其他资源才可以处理具体业务谁注入谁IoC容器向实例化类中注入了某个依赖注入了什么实例化类需要处理的依赖对象如何实现基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。基于接口。实现特定接口以供外部容器注入所依赖类型的对象。基于 set 方法。实现特定属性的 public set 方法,来让外部容器调用传入所依赖类型的对象。interface Way{ public function go();}class GoShanghai{ private $charger; public function __construct(Way $charger) { $this->charger = $charger; } public function setWay(Way $way) { $this->way = $way; } public function go() { $this->charger->go(); }}class Car implements Way{ public function go() { // TODO: Implement go() method. print_r(“我通过开车去上海”); }}// 基于构造函数和接口$goshanghai = new GoShanghai(new Car());$goshanghai->go();// 基于set方法|展示需要因为构造函数运行会出错$goshanghai = new GoShanghai();$goshanghai->setWay(new Car());$goshanghai->go();依赖查找依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。实现IoC的设计模式OP观察者模式观察者模式主要用于处理对象直接一对多多关系。当外部资源发生改变,观察者会得到通知。观察者和被观察者直接是抽象耦合的,也就不影响实例的解藕。什么是观察者观察者是一对多关系中的多,观察者需要根据目标对象改变而改变的对象。观察什么观察目标对象的某些特定的状态。如何观察观察者继承自一个抽象观察类,抽象观察类实现注册、监听和通知功能。如何实现/** * 观察者的抽象类 /abstract class Observer{ // 这是一个目标类 protected $subject; // 定义一个用于更新的抽象方法 public abstract function update();}/* * 目标类 /class Subject { // 观察者数组集合 private $observerList = array(); // 状态,这里遵循开闭原则将属性私有化 private $state = 0; /* * 获取状态的值 / public function getState() :int { return $this->state; } /* * 设置状态的值 * @param int $state / public function setState(int $state) { $this->state = $state; $this->notifyAllObservers(); } /* * 注册观察者 * @param Observer $observer / public function attach(Observer $observer) { array_push($this->observerList, $observer); } /* * 通知观察者 / public function notifyAllObservers() { foreach ($this->observerList as $observer) { $observer->update(); } }}/* * 观察者A /class AObserver extends Observer{ public function __construct(Subject $subject) { // 注入目标依赖实现IoC $this->subject = $subject; // 注册观察者 $this->subject->attach($this); } public function update() { // TODO: Implement update() method. print_r(“A观察者更新了支付业务的状态:{$this->subject->getState()}\n”); }}// 创建一个目标对象$subject = new Subject();// 为目标对象绑定观察者new AObserver($subject);print_r(“目标对象的状态:{$subject->getState()}\n”);$subject->setState(10);// 目标对象的状态:0// A观察者更新了支付业务的状态:10TP模版模式通过一个抽象基类定义执行它的模版也可以是方法,子类重写抽象方法的实现,但调用实在抽象基类中完成的,从而实现控制反转。就是基类控制行为,子类完成实现。如何实现/* * 抽象模版类 /abstract class Template{ // 抽象方法A public abstract function stepA(); // 抽象方法B public abstract function stepB(); // 执行定义的步骤, 这里使用了final关键字修饰,final的作用是使这个方法不可被继承, 这样就不会被子类执行 public final function run() { $this->stepA(); $this->stepB(); }}/* * 方案A的实例 */class FuncA extends Template{ public function stepA() { // TODO: Implement stepA() method. print_r(“方案A第一步\n”); } public function stepB() { // TODO: Implement stepB() method. print_r(“方案A第二步\n”); }}$funca = new FuncA();$funca->run();Laravel中的IoC上面的例子可以看到控制反转使每个业务实例相对对立,他们的组装在代码执行时完成,当业务简单、依赖单一时看上去没有问题,但是当依赖复杂时会导致组装变得繁琐且难以梳理。在laravel中是通过容器Container来解决这个问题。下面我们通过几句话来简单看一下laravel中容器的使用。启动laravel时就是启动了一个容器容器内的实例在一次应用程序级调用中是单例的通过make实现自动序列化依赖对象,代替new通过bind方法让每一个抽象接口和它的实例类一一对应通过resolveing方法注册一个回调callback在绑定的对象解析完之后调用 ...

March 12, 2019 · 2 min · jiezi

JS设计模式--Factory(工厂)模式

工厂模式提供一个通用的接口来创建对象示例 //Car构造函数 function Car(option) { this.doors = option.doors || 4 this.color = option.color || ‘red’ this.state = option.state || ‘brand new’ } //Truck构造函数 function Truck(option) { this.color = option.color || ‘blue’ this.wheelSize = option.wheelSize || ’large’ this.state = option.state || ‘used’ } //Vehicle工厂 function VehicleFactory() {} VehicleFactory.prototype.vehicleClass = Car VehicleFactory.prototype.createVehicle = function(option) { if(option.vehicleType == ‘car’) { this.vehicleClass = Car }else { this.vehicleClass = Truck } return new this.vehicleClass(option) } //创建生成汽车的工厂实例 var carFactory = new VehicleFactory() var car = carFactory.createVehicle({ vehicleType: ‘car’, color: ‘yellow’, doors: 6 }) console.log(car instanceof Car) console.log(car) //true //Car {doors: 6, color: “yellow”, state: “brand new”} var movingTruck = carFactory.createVehicle({ vehicleType: ’truck’, color: ‘red’, state: ’like new’, wheelSize: ‘small’ }) console.log(movingTruck instanceof Truck) console.log(movingTruck) //true //Truck {color: “red”, state: “like new”, wheelSize: “small”}适用场景当对象或组建设置涉及高复杂性时当需要根据所在当不同环境轻松生成对象当不同实例时当处理很多共享相同属性当小型对象或组件时在编写只需要满足一个API契约(亦称鸭子类型)的其他对象的实例对象时。对解耦是很有用对。JS设计模式系列文章JS设计模式之Factory(工厂)模式JS设计模式之Singleton(单例)模式JS设计模式之Facade(外观)模式JS设计模式之Module(模块)模式、Revealing Module(揭示模块)模式 ...

March 12, 2019 · 1 min · jiezi

行为型模式:观察者模式

行为型模式:观察者模式十一大行为型模式之七:观察者模式。简介姓名 :观察者模式英文名 :Observer Pattern价值观 :盯着你怎么着个人介绍 :Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。(来自《设计模式之禅》)你要的故事想来想去,就拿我们现在生活中最常体会到的事情来讲观察者模式–朋友圈。小明、小红、小东 3 人是好朋友,最近他们的父母都给安排了手机,刚用上手机那是相当的兴奋呀。他们立马从 QQ 转投到微信的怀抱,对微信的朋友圈玩的不亦乐乎,什么事情都往上面发。突然有一天,小明和小红因为一些小事争执闹别扭了,原因就是他们对一道数学题有不同的见解。就跟我们小时候和朋友玩得好好的,突然因为一点小事就闹翻了。小红比较孩子气,立马就屏蔽了小明的朋友圈,不想再看到有关小明相关的信息。故事就是这么一回事,关注点就在这朋友圈上。朋友圈就是运用观察者模式的一个很好的样例。为什么这么说?我们发朋友圈的时候,那些没有屏蔽我们朋友圈的好友,会收到信息推送。也就是没有屏蔽我们朋友圈的好友其实是订阅了我们朋友圈,好友相当于观察者,我们是被观察的对象。符合观察者模式这个关系。我们通过代码来描述小明、小红、小东他们在朋友圈玩的场景。利用观察者模式,需要观察对象和被观察对象,所以我们先定义 2 个接口,分别是 Observable (可被观察接口) 和 Observer (观察者接口)。实现 Observable 接口的对象说明是可被订阅观察的,所以它需要 addObserver() 新增订阅者方法和 removeObserver() 移除订阅者方法,另外还有一个是必须的,就是通知各个订阅者消息的方法 notifyObservers()。那 Observable 接口代码如下所示。interface Observable { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(String message);}实现 Observer 接口的对象说明是可以去订阅观察的,也就是说可以接收被订阅的对象发出来的消息,那就需要一个接收消息的方法 update()。代码如下所示。interface Observer { void update(String name, String message);}为了让大家不混淆,先把观察者和被观察者分离开,其实在这个例子中,观察者和被观察者是同一个对象 User 的。这里就分开,分成 User 和 Friend,后面会给出正确的代码,稍安勿躁哈。这里 User 作为被观察者,实现了 Observable 接口,而 Friend 作为观察者,实现了 Observer 接口。代码如下。class User implements Observable { private List<Observer> friends; private String name; public User(String name) { this.name = name; this.friends = new LinkedList<>(); } public void sendMessage(String message) { this.notifyObservers(message); } @Override public void addObserver(Observer observer) { this.friends.add(observer); } @Override public void removeObserver(Observer observer) { this.friends.remove(observer); } @Override public void notifyObservers(String message) { this.friends.forEach(friend -> { friend.update(this.name, message); }); }}class Friend implements Observer { private String name; public Friend(String name) { this.name = name; } @Override public void update(String name, String message) { System.out.println("【" + this.name + “】看到【” + name + “】发的朋友圈:” + message); }}public class ObserverTest { public static void main(String[] args) { User xiaoMing = new User(“小明”); Friend xiaoHong = new Friend(“小红”); Friend xiaoDong = new Friend(“小东”); xiaoMing.addObserver(xiaoHong); xiaoMing.addObserver(xiaoDong); xiaoMing.sendMessage(“今天真开心”); // 小红和小明闹别扭了,小红取消订阅小明的朋友圈 xiaoMing.removeObserver(xiaoHong); xiaoMing.sendMessage(“希望明天也像今天一样开心”); }}打印结果:【小红】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心看到代码执行结果,小红和小东都订阅了小明的朋友圈,小明发了朋友圈:今天真开心。他们俩都收到了,因为小红和小明闹别扭,小红取消订阅小明的朋友圈,所以小明后来发的朋友圈,小红没收到。上面代码其实是不对的,不应该用 User 和 Friend 2 个类来定义。如果小明订阅小红和小东的朋友圈呢?这样实现比较麻烦,主要是为了分清 观察者 和 被观察者 这 2 个概念,通过上面的例子应该分清楚了 2 个概念了,那就可以来看正确的代码,小明、小红、小东他们其实都是观察者和被观察者,所以我们用 User2 来定义他们就可以,User2 实现了 Observable 和 Observer 接口。代码如下。class User2 implements Observable, Observer { private List<Observer> friends; private String name; public User2(String name) { this.name = name; this.friends = new LinkedList<>(); } @Override public void addObserver(Observer observer) { this.friends.add(observer); } @Override public void removeObserver(Observer observer) { this.friends.remove(observer); } @Override public void notifyObservers(String message) { this.friends.forEach(friend -> { friend.update(this.name, message); }); } @Override public void update(String name, String message) { System.out.println("【" + this.name + “】看到【” + name + “】发的朋友圈:” + message); } public void sendMessage(String message) { this.notifyObservers(message); }}public class ObserverTest { public static void main(String[] args) { User2 xiaoMing2 = new User2(“小明”); User2 xiaoHong2 = new User2(“小红”); User2 xiaoDong2 = new User2(“小东”); xiaoMing2.addObserver(xiaoHong2); xiaoMing2.addObserver(xiaoDong2); xiaoMing2.sendMessage(“今天真开心”); xiaoMing2.removeObserver(xiaoHong); xiaoMing2.sendMessage(“希望明天也像今天一样开心”); xiaoHong2.addObserver(xiaoMing2); xiaoHong2.addObserver(xiaoDong2); xiaoHong2.sendMessage(“今天和小明吵架了,屏蔽他的朋友圈”); xiaoDong2.addObserver(xiaoMing2); xiaoDong2.addObserver(xiaoHong2); xiaoDong2.sendMessage(“小明和小红吵架了,夹在中间好尴尬”); }}打印结果:【小红】看到【小明】发的朋友圈:今天真开心【小东】看到【小明】发的朋友圈:今天真开心【小红】看到【小明】发的朋友圈:希望明天也像今天一样开心【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心【小明】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈【小东】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈【小明】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬【小红】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬从代码中,我们看到小明、小红、小东 3 个人互相订阅朋友圈,当然中途小红屏蔽了小明的朋友圈。这就是 观察者 和 被观察者 刚好是同一个对象的实现。总结观察者模式 是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。好,观察者模式就到这。推荐阅读:行为型模式:迭代器模式行为型模式:策略模式行为型模式:责任链模式设计模式系列文章持续更新中,欢迎关注公众号 LieBrother,一起交流学习。 ...

March 11, 2019 · 2 min · jiezi

设计模式手册之状态模式

什么是“状态模式”?状态模式:对象行为是基于状态来改变的。内部的状态转化,导致了行为表现形式不同。所以,用户在外面看起来,好像是修改了行为。Webpack4系列教程(17篇) + 设计模式手册(16篇):GitHub地址博客主题推荐:Theme Art Design,“笔记记录+搭建知识体系”的利器。原文地址: 设计模式手册之状态模式2. 优缺点优点封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。每个状态都是确定的,所以对象行为是可控的。缺点状态模式的关键是将事物的状态都封装成单独的类,这个类的各种方法就是“此种状态对应的表现行为”。因此,状态类会增加程序开销。3. 代码实现3.1 ES6 实现在JavaScript中,可以直接用JSON对象来代替状态类。下面代码展示的就是FSM(有限状态机)里面有3种状态:download、pause、deleted。控制状态转化的代码也在其中。DownLoad类就是,常说的Context对象,它的行为会随着状态的改变而改变。const FSM = (() => { let currenState = “download”; return { download: { click: () => { console.log(“暂停下载”); currenState = “pause”; }, del: () => { console.log(“先暂停, 再删除”); } }, pause: { click: () => { console.log(“继续下载”); currenState = “download”; }, del: () => { console.log(“删除任务”); currenState = “deleted”; } }, deleted: { click: () => { console.log(“任务已删除, 请重新开始”); }, del: () => { console.log(“任务已删除”); } }, getState: () => currenState };})();class Download { constructor(fsm) { this.fsm = fsm; } handleClick() { const { fsm } = this; fsm[fsm.getState()].click(); } hanldeDel() { const { fsm } = this; fsm[fsm.getState()].del(); }}// 开始下载let download = new Download(FSM);download.handleClick(); // 暂停下载download.handleClick(); // 继续下载download.hanldeDel(); // 下载中,无法执行删除操作download.handleClick(); // 暂停下载download.hanldeDel(); // 删除任务3.2 Python3 实现python的代码采用的是“面向对象”的编程,没有过度使用函数式的闭包写法(python写起来也不难)。因此,负责状态转化的类,专门拿出来单独封装。其他3个状态类的状态,均由这个状态类来管理。# 负责状态转化class StateTransform: def init(self): self.__state = ‘download’ self.__states = [‘download’, ‘pause’, ‘deleted’] def change(self, to_state): if (not to_state) or (to_state not in self.__states) : raise Exception(‘state is unvalid’) self.__state = to_state def get_state(self): return self.__state# 以下是三个状态类class DownloadState: def init(self, transfomer): self.__state = ‘download’ self.__transfomer = transfomer def click(self): print(‘暂停下载’) self.__transfomer.change(‘pause’) def delete(self): print(‘先暂停, 再删除’) class PauseState: def init(self, transfomer): self.__state = ‘pause’ self.__transfomer = transfomer def click(self): print(‘继续下载’) self.__transfomer.change(‘download’) def delete(self): print(‘删除任务’) self.__transfomer.change(‘deleted’)class DeletedState: def init(self, transfomer): self.__state = ‘deleted’ self.__transfomer = transfomer def click(self): print(‘任务已删除, 请重新开始’) def delete(self): print(‘任务已删除’)# 业务代码class Download: def init(self): self.state_transformer = StateTransform() self.state_map = { ‘download’: DownloadState(self.state_transformer), ‘pause’: PauseState(self.state_transformer), ‘deleted’: DeletedState(self.state_transformer) } def handle_click(self): state = self.state_transformer.get_state() self.state_map[state].click() def handle_del(self): state = self.state_transformer.get_state() self.state_map[state].delete()if name == ‘main’: download = Download() download.handle_click(); # 暂停下载 download.handle_click(); # 继续下载 download.handle_del(); # 下载中,无法执行删除操作 download.handle_click(); # 暂停下载 download.handle_del(); # 删除任务4. 参考23种设计模式全解析菜鸟教程状态模式《JavaScript设计模式与开发实践》

March 11, 2019 · 2 min · jiezi

轻松学会代理模式

代理模式在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。介绍意图:为其他对象提供一种代理以控制对这个对象的访问。主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。何时使用:想在访问一个类时做一些控制。如何解决:增加中间层。关键代码:实现与被代理类组合。应用实例:Windows 里面的快捷方式。买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。优点: 1、职责清晰。 2、高扩展性。 3、智能化。缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类以游戏买票为例:创建一个接口 Ticketpublic interface Ticket { void goToMap();}创建一个代理类,实现 该接口 ProxyTicketpublic class ProxyTicket implements Ticket { private RealTicket realTicket; private String ticketName; public ProxyTicket(String ticketName) { this.ticketName = ticketName; } @Override public void goToMap() { System.out.println(“开始进入地下城…”); if(realTicket == null){ realTicket = new RealTicket(ticketName); } realTicket.goToMap(); }}创建一个具体类实现该接口 RealTicketpublic class RealTicket implements Ticket{ private String ticketName; public RealTicket(String ticketName) { this.ticketName = ticketName; buyTicket(ticketName); } @Override public void goToMap() { System.out.println(“当前玩家拥有通行证,进入地下城…”); } private void buyTicket(String ticketName){ System.out.println(“当前玩家没有通行证,RealTicket开始购买” + ticketName); }}创建 MapDemo 测试:public class MapDemo { public static void main(String[] args) { Ticket ticket = new ProxyTicket(“深渊通行证”); ticket.goToMap(); System.out.println(“再次挑战”); ticket.goToMap(); }}测试结果:开始进入地下城…当前玩家没有通行证,RealTicket开始购买深渊通行证当前玩家拥有通行证,进入地下城…再次挑战开始进入地下城…当前玩家拥有通行证,进入地下城…当玩家进入地图时会判断是否拥有通行证,没有则使用代理类RealTicket去购买 ...

March 11, 2019 · 1 min · jiezi

PHP面试常考之设计模式——工厂模式

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.11至3.15)的一三五更新的文章如下:周一:PHP面试常考之设计模式——工厂模式周三:PHP面试常考之设计模式——建造者模式周五:PHP面试常考之设计模式——策略模式今天这篇文章主要讲解的是PHP面试常考的设计模式之工厂模式。工厂模式其实可以划分为:简单工厂模式、工厂方法模式、抽象工厂模式等。具体它们有什么区别,用途有哪些呢?以下我将进行讲解。自己整理了一篇“设计模式需要遵守的5大原则”的文章,关注公众号:“琉忆编程库”,回复:“原则”,我发给你。以下内容如需转载,请注明作者和出处。1、简单工厂模式介绍:简单工厂模式又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。UML图:说明:CashFactory类:负责创建具体产品的实例CashSuper类:抽象产品类,定义产品子类的公共接口CreateCashAccept 类:具体产品类,实现Product父类的接口功能,也可添加自定义的功能简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。实现示例:<?php //简单工厂模式class Cat{ function __construct() { echo “I am Cat class <br>”; }}class Dog{ function __construct() { echo “I am Dog class <br>”; }}class Factory{ public static function CreateAnimal($name){ if ($name == ‘cat’) { return new Cat(); } elseif ($name == ‘dog’) { return new Dog(); } }}$cat = Factory::CreateAnimal(‘cat’);$dog = Factory::CreateAnimal(‘dog’);2、工厂方法模式介绍:工厂方法模式通过定义一个抽象的核心工厂类,并定义创建产品对象的接口,创建具体产品实例的工作延迟到其工厂子类去完成。这样做的好处是核心类只关注工厂类的接口定义,而具体的产品实例交给具体的工厂子类去创建。当系统需要新增一个产品是,无需修改现有系统代码,只需要添加一个具体产品类和其对应的工厂子类,是系统的扩展性变得很好,符合面向对象编程的开闭原则;UML图:说明:Product:抽象产品类ConcreteProduct:具体产品类Factory:抽象工厂类ConcreteFactory:具体工厂类工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。实现示例:<?php interface Animal{ public function run(); public function say();}class Cat implements Animal{ public function run(){ echo “I ran slowly <br>”; } public function say(){ echo “I am Cat class <br>”; }}class Dog implements Animal{ public function run(){ echo “I’m running fast <br>”; } public function say(){ echo “I am Dog class <br>”; }}abstract class Factory{ abstract static function createAnimal();}class CatFactory extends Factory{ public static function createAnimal() { return new Cat(); }}class DogFactory extends Factory{ public static function createAnimal() { return new Dog(); }}$cat = CatFactory::createAnimal();$cat->say();$cat->run();$dog = DogFactory::createAnimal();$dog->say();$dog->run();自己整理了一篇“设计模式需要遵守的5大原则”的文章,关注公众号:“琉忆编程库”,回复:“原则”,我发给你。3、抽象工厂模式介绍:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。此模式是对工厂方法模式的进一步扩展。在工厂方法模式中,一个具体的工厂负责生产一类具体的产品,即一对一的关系,但是,如果需要一个具体的工厂生产多种产品对象,那么就需要用到抽象工厂模式了。为了便于理解此模式,这里介绍两个概念:产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。UML类图:说明:具体类图的功能可以看UML图的说明实现示例:<?php interface TV{ public function open(); public function use();}class HaierTv implements TV{ public function open() { echo “Open Haier TV <br>”; } public function use() { echo “I’m watching TV <br>”; }}interface PC{ public function work(); public function play();}class LenovoPc implements PC{ public function work() { echo “I’m working on a Lenovo computer <br>”; } public function play() { echo “Lenovo computers can be used to play games <br>”; }}abstract class Factory{ abstract public static function createPc(); abstract public static function createTv();}class ProductFactory extends Factory{ public static function createTV() { return new HaierTv(); } public static function createPc() { return new LenovoPc(); }}$newTv = ProductFactory::createTV();$newTv->open();$newTv->use();$newPc = ProductFactory::createPc();$newPc->work();$newPc->play();自己编写的《PHP程序员面试笔试宝典》和《PHP程序员面试笔试真题解析》书籍,已在各大电商平台销售,两本可以帮助你更快更好的拿到offer的书。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。 ...

March 11, 2019 · 2 min · jiezi

设计模式之面向切面编程AOP

设计模式之面向切面编程AOP动态的将代码切入到指定的方法、指定位置上的编程思想就是面向切面的编程。代码只有两种,一种是逻辑代码、另一种是非逻辑代码。逻辑代码就是实现功能的核心代码,非逻辑代码就是处理琐碎事务的代码,比如说获取连接和关闭连接,事务开始,事务提交还有log等任何与核心逻辑无关的功能。为什么要用面向切面编程AOP假如说DB的相关开发,我们需要每次在相关功能之前需要连接数据库,在每次在功能之后要关闭连接。每次开发的时候都需要再写一遍这些非逻辑代码,而AOP的思想就是将这些非逻辑代码提取出来,我们只考虑逻辑代码就行了。把框框设计好,这里写前面的连接数据库,这里写逻辑,这里写后面的关闭连接。面向切面编程的例子CSS就是最简单的例子,HTML页面就是从上到下渲染的,遇到一个CSS就加载一个.a some html taga.cssb some html tagb.css一开始,会加载a some html tag ,然后会加载a.css然后,会加载b some html tag ,最后加载 b.css。参考

March 8, 2019 · 1 min · jiezi

轻松学会观察者模式

观察者模式当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。介绍意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。如何解决:使用面向对象技术,可以将这种依赖关系弱化。应用实例:1.拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。2.打团时每个职业做不同的事情。优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。简单的说就是:多个对象去观察一个对象是否发出号令(消息),然后做出相应响应动作以DNF打团为例子:当团长发出攻坚开始的消息时所有的队友开始输出。创建一个职业的抽象类(观察者接口):Heropublic abstract class Hero { /** * 打团 * @param msg */ public abstract void datuan(String msg);}分别创建三个职业(观察者)创建一个红眼:Hongyanpublic class Hongyan extends Hero { private static final String ROLE = “帝血弑天”; @Override public void datuan(String msg) { System.out.println(msg+ ROLE +":血魔:弑天! “); }}创建一个奶爸:Naibapublic class Naiba extends Hero { private static final String ROLE = “神思者”; @Override public void datuan(String msg) { System.out.println(msg + ROLE + “: 阿波克列!”); }}创建一个篮拳:Lanquanpublic class Lanquan extends Hero { private static final String ROLE = “正义仲裁者”; @Override public void datuan(String msg) { System.out.println(msg+ ROLE + “:粉碎吧!”); }}创建一个团长(被观察者):Captainpublic class Captain { private List<Hero> heroes = new ArrayList<>(); public void setMsg(String msg) { notifyAll(msg); } //订阅 public void addTeam(Hero hero) { heroes.add(hero); } //通知所有订阅的观察者 private void notifyAll(String msg) { for (Hero observer : heroes) { observer.datuan(msg); } }}主程序:Mainpublic class Main { public static void main(String[] args) { Lanquan lanquan = new Lanquan(); Hongyan hongyan = new Hongyan(); Naiba naiba = new Naiba(); Captain captain = new Captain(); captain.addTeam(lanquan); captain.addTeam(hongyan); captain.addTeam(naiba); captain.setMsg(“超时空攻坚战开始-”); }}执行结果:超时空攻坚战开始-正义仲裁者:粉碎吧!超时空攻坚战开始-帝血弑天:血魔:弑天! 超时空攻坚战开始-神思者: 阿波克列! ...

March 7, 2019 · 1 min · jiezi

Go设计模式学习笔记

学习对象:https://github.com/tmrts/go-p…。这个repo使用go语言实现了一些设计模式,包括常用的Builder模式,Singleton模式等,也有列举出还未用go实现的模式,如Bridge模式等。本文并非完整地介绍和解析这个repo里的每一行代码,只对个人认为值得学习和记录的地方进行说明,阅读过repo代码后再阅读本文比较合适。Functional Options这个模式是一种优雅地设置对象初始化参数的方式。考虑的点是:如何友好地扩展初始化的选填参数如何友好地处理默认值问题函数签名见名知意比较以下几种初始化对象参数的方法://name是必填参数, timeout和maxConn是选填参数,如果不填则设置为默认值// pattern #1func NewServer(name string, timeout time.Duration, maxConn uint) (*Server, error) {…}// 这种方法最直观, 但也是最不合适的, 因为对于扩展参数需要修改函数签名, 且默认值需要通过文档获知// pattern #2type ServerConf struct { Timeout time.Duration MaxConn uint}func NewServer(name string, conf ServerConf) (*Server, error) {…} // 1)func NewServer(name string, conf *ServerConf) (*Server, error) {…} // 2)func NewServer(name string, conf …ServerConf) (*Server, error) {…} // 3)// 改进: 使用了参数结构体, 增加参数不需要修改函数签名// 1) conf现在是必传, 实际上里面的是选填参数// 2) 避免nil; conf可能在外部被改变.// 3) 都使用默认值的时候可以不传, 但多个conf可能在配置上有冲突// conf的默认空值对于Server可能是有意义的.// pattern #3: Functional Optionstype ConfSetter func(srv *Server) errorfunc ServerTimeoutSetter(t time.Duration) ConfSetter { return func(srv *Server) error { srv.timeout = t return nil }}func ServerMaxConnSetter(m uint) ConfSetter { return func(srv *Server) error { srv.maxConn = m return nil }}func NewServer(name string, setter …ConfSetter) (*Server, error) { srv := new(Server) … for _, s := range setter { err := s(srv) } …}// srv, err := NewServer(“name”, ServerTimeoutSetter(time.Second))// 使用闭包作为配置参数. 如果不需要配置选填参数, 只需要填参数name.上面的pattern#2尝试了三种方法来优化初始化参数的问题,但每种方法都有自己的不足之处。pattern#3,也就是Functional Options,通过使用闭包来做优化,从使用者的角度来看,已经是足够简洁和明确了。当然,代价是初次理解这种写法有点绕,不如前两种写法来得直白。trade off欲言又止稍加思考,容易提出这个问题:这跟Builder模式有什么区别呢?个人认为,Functional Options模式本质上就是Builder模式:通过函数来设置参数。参考文章:Functional options for friendly APIsCircuit-Breaker熔断模式:如果服务在一段时间内不可用,这时候服务要考虑主动拒绝请求(减轻服务方压力和请求方的资源占用)。等待一段时间后(尝试等待服务变为可用),服务尝试接收部分请求(一下子涌入过多请求可能导致服务再次不可用),如果请求都成功了,再正常接收所有请求。// 极其精简的版本, repo中版本详尽一些type Circuit func() error// Counter 的实现应该是一个状态机type Counter interface { OverFailureThreshold() UpdateFailure() UpdateSuccess()}var cnt Counterfunc Breaker(c Circuit) Circuit { return func() { if cnt.OverFailureThreshold() { return fmt.Errorf(“主动拒绝”) } if err := c(); err != nil { cnt.UpdateFailure() return err } cnt.UpdateSuccess() return nil }}熔断模式更像是中间件而不是设计模式:熔断器是一个抽象的概念而不是具体的代码实现;另外,如果要实现一个实际可用的熔断器,要考虑的方面还是比较多的。举些例子:需要提供手动配置熔断器的接口,避免出现不可控的请求情况;什么类型的错误熔断器才生效(恶意发送大量无效的请求可能导致熔断器生效),等等。参考文章:Circuit Breaker pattern参考实现:gobreakerSemaphorego的标准库中没有实现信号量,repo实现了一个:)repo实现的实质是使用chan。chan本身已经具备互斥访问的功能,而且可以设定缓冲大小,只要稍加修改就可以当作信号量使用。另外,利用select语法,可以很方便地实现超时的功能。type Semaphore struct { resource chan struct{} // 编译器会优化struct{}类型, 使得所有struct{}变量都指向同一个内存地址 timeout time.Duration // 用于避免长时间的死锁}type TimeoutError errorfunc (s *Semaphore) Aquire() TimeoutError { select { // 会从上到下检查是否阻塞 // 如果timeout为0, 且暂时不能获得/解锁资源, 会立即返回超时错误 case: <-s.resource: return nil case: <- time.After(s.timeout): return fmt.Errorf(“timeout”) } }func (s *Semaphore) Release() TimeoutError { select { // 同Aquire() case: s.resource <- struct{}{}: return nil case: <- time.After(s.timeout): return fmt.Errorf(“timeout”) } }func NewSemaphore(num uint, timeout time.Duration) (*Semaphore, error) { if num == 0 { return fmt.Errorf(“invalid num”) //如果是0, 需要先Release才能Aquire. } return &Semaphore{ resource: make(chan strcut{}, num), timeout: timeout, }, nil //其实返回值类型也不影响Semaphore正常工作, 因为chan是引用类型}Object Pool标准库的sync包已经有实现了一个对象池,但是这个对象池接收的类型是 interface{} (万恶的范型),而且池里的对象如果不被其它内存引用,会被gc回收(同java中弱引用的collection类型类似)。repo实现的对象池是明确类型的(万恶的范型+1),而且闲置不会被gc回收。但仅仅作为展示说明,repo的实现没有做超时处理。下面的代码尝试加上超时处理。也许对使用者来说,额外增加处理超时错误的代码比较繁琐,但这是有必要的,除非使用者通读并理解了你的代码。trade offtype Pool struct { pool chan *Object timeout time.Duration}type TimeoutError errorfunc NewPool(total int, timeout time.Duration) *Pool { p := &Pool { pool: make(Pool, total), timeout: timeout, } //pool是引用类型, 所以返回类型可以不是指针 for i := 0; i < total; i++ { p.pool <- new(Object) } return p}func (p *Pool) Aquire() (*Object, TimeoutError) { select { case obj <- p.pool: return obj, nil case <- time.After(timeout): return nil, fmt.Errorf(“timeout”) }}func (p *Pool) Release(obj *Object) TimeoutError { select { case p.pool <- obj: return nil case <- time.After(timeout): return nil, fmt.Errorf(“timeout”) }}chan and goroutine解析一下repo里goroutine和chan的使用方式,也不算是设计模式。Fan-in pattern 主要体现如何使用sync.WaitGroup同步多个goroutine。思考:这里的实现是如果cs的长度为n, 那个要开n个goroutine, 有没有办法优化为开常数个goroutine?// 将若干个chan的内容合并到一个chan当中func Merge(cs …<-chan int) <-chan int { out := make(chan int) var wg sync.WaitGroup wg.Add(len(cs)) // 将send函数在for循环中写成一个不带参数的匿名函数, 看起来会使代码更简洁, // 但实际上所有for循环里的所有goroutine会公用一个c, 代码不能正确实现功能. send := func(c <-chan int) { for n := range c { out <- n } wg.Done() } for _, c := range cs { go send(c) } // 开一个goroutine等待wg, 然后关闭merge的chan, 不阻塞Merge函数 go func() { wg.Wait() close(out) } return out}Fan-out pattern 将一个主chan的元素循环分发给若干个子chan(分流)。思路比较简单就不贴代码了。思考:reop实现的代码,如果其中一个子chan没有消费元素,那么整个分发流程都会卡住。是否可以优化?Bounded Parallelism Pattern 比较完成的例子来说明如何时候goroutine. 并发计算目录下文件的md5.func MD5All(root string) (map[string][md5.Size]byte, error) { //因为byte是定长的, 使用数据更合适, 可读且性能也好一点 done := make(chan struct{}) //用于控制整个流程是否暂停. 其实这里是用context可能会更好. defer close(done) paths, errc := walkFiles(done, root) c := make(chan result) var wg sync.WaitGroup const numDigesters = 20 wg.Add(numDigesters) for i := 0; i < numDigesters; i++ { go func() { digester(done, paths, c) wg.Done() }() } // 同上, 开goroutine等待所有digester结束 go func() { wg.Wait() close(c) }() m := make(map[string][md5.Size]byte) for r := range c { if r.err != nil { return nil, r.err } m[r.path] = r.sum } // 必须放在m处理结束后才检查errc. 否则, 要等待walkFiles结束了才能开始处理m // 相反, 如果errc有信号, c肯定已经close了 if err := <-errc; err != nil { return nil, err } return m, nil}func walkFiles(done <-chan struct{}, root string) (<-chan string, <-chan error) { paths := make(chan string) // 这里可以适当增加缓冲, 取决于walkFiles快还是md5.Sum快 errc := make(chan error, 1) //必须有缓冲, 否则死锁. 上面的代码paths close了才检查errc go func() { defer close(paths) // 这里的defer不必要. defer是运行时的, 有成本. errc <- filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.Mode().IsRegular() { return nil } select { case paths <- path: case <-done: return errors.New(“walk canceled”) } return nil }) }() return paths, errc}type result struct { path string sum [md5.Size]byte err error}func digester(done <-chan struct{}, paths <-chan string, c chan<- result) { for path := range paths { data, err := ioutil.ReadFile(path) select { // 看md5.Sum先结束还是done信号先到来 case c <- result{path, md5.Sum(data), err}: case <-done: return } }} ...

March 6, 2019 · 4 min · jiezi

轻松学会责任链模式

设计模式 - 责任链模式顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。简单的说就是: 你处理不了的事情就交给你的下一级(级别更高)的去处理。介绍:意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。在某一场景是可以减少大量的if else代码优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。话不多说直接上代码创建一个抽象的程序员类:AbstractCoderpublic abstract class AbstractCoder { public static int SIMPLEBUG = 1; public static int COMPLEXBUG = 2; public static int DIFFICULTBUG = 3; protected int level; protected AbstractCoder nextCoder; public void setNextCoder(AbstractCoder nextCoder) { this.nextCoder = nextCoder; } public void resolveBug(int level, String message) { //大于处理的权限 if (this.level < level) { nextCoder.resolveBug(level, message); } else { write(message); } } abstract protected void write(String message);}创建不同的程序员类去扩展抽象类,每个程序员做着不同的事情。LongHairCoder:public class LongHairCoder extends AbstractCoder { public LongHairCoder(int level) { this.level = level; } @Override protected void write(String message) { System.out.println(“初级程序员处理:” + message); }}ShortHairCoder:public class ShortHairCoder extends AbstractCoder { public ShortHairCoder(int level) { this.level = level; } @Override protected void write(String message) { System.out.println(“秃头程序员处理:” + message); }}BaldHeadCoder:public class BaldHeadCoder extends AbstractCoder { public BaldHeadCoder(int level) { this.level = level; } @Override protected void write(String message) { System.out.println(“光头程序员处理:” + message); }}实现责任链: ChainPatternDemopublic class ChainPattern { public static AbstractCoder getChainOfLoggers() { AbstractCoder longHairCoder = new LongHairCoder(AbstractCoder.SIMPLEBUG); AbstractCoder shortHairCoder = new ShortHairCoder(AbstractCoder.COMPLEXBUG); AbstractCoder baldHeadCoder = new BaldHeadCoder(AbstractCoder.DIFFICULTBUG); longHairCoder.setNextCoder(shortHairCoder); shortHairCoder.setNextCoder(baldHeadCoder); return longHairCoder; } public static void main(String[] args) { AbstractCoder abstractCoder = getChainOfLoggers(); abstractCoder.resolveBug(AbstractCoder.SIMPLEBUG, “一个简单的bug”); abstractCoder.resolveBug(AbstractCoder.COMPLEXBUG, “一个复杂的bug”); abstractCoder.resolveBug(AbstractCoder.DIFFICULTBUG, “一个困难的bug”); }}执行结果:初级程序员处理:一个简单的bug秃头程序员处理:一个复杂的bug光头程序员处理:一个困难的bug ...

March 6, 2019 · 1 min · jiezi

PHP设计模式

简单工厂模式// 共同接口interface db{ function conn();}// 服务器端开发(不知道将会被谁调用)class dbsqlite implements db{ public function conn(){ echo ‘连接上了sqlite’; }}class dbmysql implements db{ public function conn(){ echo ‘连接上了mysql’; }}class Factory{ public static function creatDB($type){ if($type == ‘mysql’){ return new dbmysql(); }elseif($type == ‘sqlite’){ return new dbsqlite(); }else{ throw new Exception(“Error DB type”, 1); } }}// 客户端调用时,不知道工厂类中实例化的几种类,只需要传递$type参数就可以$db = Factory::creatDB(‘mysql’);$db->conn();

March 5, 2019 · 1 min · jiezi

设计模式——观察者模式

观察者模式顾名思义,有观察者 被观察者, 这两是有关系的被观察者状态改变时,触发观察者的 动作// 被观察者function Observer(){ this.state = ‘默认状态’; this.arr = [];// 用来存储所有的观察者}// 知道谁在 观察自己Observer.prototype.attch = function(s){ // 存储主动观察者 this.arr.push(s);}// 被观察者状态Observer.prototype.setState = function(newState){ this.state = newState; // 只要状态改变就通知所有的观察者,孩子饿了就会朝着爸妈哭 this.arr.forEach(s=>s.update())}// 定义观察者/*** name: 观察者* target: 被观察者**/ function Subject(name,target){ this.name = name; this.target = target;}// 得到状态后的反应Subject.prototype.update = function(newState){ console.log(this.name + ‘观察到状态’ + newState);}let o = new Observer();let s1 = new Subject(‘父亲’,o);let s2 = new Subject(‘母亲’,o);o.attch(s1);o.attch(s2);o.setState(‘饿了’);

March 2, 2019 · 1 min · jiezi

行为型模式:迭代器模式

LieBrother原文:行为型模式:迭代器模式十一大行为型模式之六:迭代器模式。简介姓名 :迭代器模式英文名 :Iterator Pattern价值观 :人生没有回头路个人介绍 :Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。(来自《设计模式之禅》)你要的故事大家伙听歌频率高么?是不是经常听歌曲来放松心情?我是经常会听歌,心情不好的时候听歌,心情好的时候也听歌。。。今天讲的迭代器模式,我们就拿听歌这件事来说说,大家都知道听歌有几种模式:单曲循环、列表循环、随机等等。。。现在网易云音乐还多了一个心动模式。既然说到迭代器模式,那这里就要着重讲讲列表循环这个听歌模式,其他的就先抛到脑后。在列表循环中,歌曲从第一条播放到最后一条,也就是一个遍历歌单的过程。我们有 2 种实现方式,一种是没有迭代器,通过获取歌单,用 for 循环遍历每一个歌曲,然后播放;另外一种是使用迭代器,获取歌单的一个迭代器,通过迭代器来遍历每一个歌曲,然后播放。下面我们就用代码来实现这 2 种方式。木有迭代器public class NoIteratorTest { public static void main(String[] args) { NetEaseMusic1 netEaseMusic1 = new NetEaseMusic1(); netEaseMusic1.listenToMusicByLoop(); }}/** * 网易云音乐 /class NetEaseMusic1 { private IList1 songList; public NetEaseMusic1() { songList = new SongList1(3); songList.add(new Song(“让我留在你身边”, “陈奕迅”)); songList.add(new Song(“你曾是少年”, “SHE”)); songList.add(new Song(“Perfect”, “Ed Sheeran”)); } /* * 列表循环 / public void listenToMusicByLoop() { for (int i = 0; i < songList.size(); i++) { System.out.println(“听歌:” + ((ISong)songList.get(i)).getSongInfo()); } }}/* * 容器接口 /interface IList1 { void add(Object object); Object get(int index); int size();}/* * 歌单 /class SongList1 implements IList1 { private ISong[] songs; private int index; private int size; public SongList1(int size) { songs = new ISong[size]; index = 0; size = 0; } @Override public void add(Object object) { songs[index++] = (ISong) object; size ++; } @Override public Object get(int index) { if (index < size) { return songs[index]; } return null; } @Override public int size() { return size; }}/* * 歌曲接口 /interface ISong { String getSongInfo();}/* * 歌曲 /class Song implements ISong{ private String name; private String singer; public Song(String name, String singer) { this.name = name; this.singer = singer; } @Override public String getSongInfo() { return this.name + “–” + this.singer; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSinger() { return singer; } public void setSinger(String singer) { this.singer = singer; }}打印结果:听歌:让我留在你身边–陈奕迅听歌:你曾是少年–SHE听歌:Perfect–Ed Sheeran我们定义了 ISong 接口,里面有个 getSongInfo() 方法来获取歌曲信息,用 Song 类来定义歌曲。没有用 Java 自带的容器来存放歌曲,这里实现了一个自定义容器接口 IList1,定义 SongList1 来做歌曲的容器,为什么不用 Java 自带的 ArrayList 等等?因为 Java 自带的已经实现了迭代器功能了,我们这里自定义其实就是在模仿自带的容器的实现。NetEaseMusic1 类是充当网易云音乐客户端,在 listenToMusicByLoop() 方法中,我们可以看到是获取了歌单 songList,然后一个一个遍历,这是没有使用迭代器的代码。下面看一下使用迭代器的代码是怎么样的。用迭代器实现遍历public class IteratorTest { public static void main(String[] args) { NetEaseMusic2 netEaseMusic2 = new NetEaseMusic2(); netEaseMusic2.listenToMusicByLoop(); }}/* * 网易云音乐 /class NetEaseMusic2{ private IList2 songList; public NetEaseMusic2() { songList = new SongList2(3); songList.add(new Song(“让我留在你身边”, “陈奕迅”)); songList.add(new Song(“你曾是少年”, “SHE”)); songList.add(new Song(“Perfect”, “Ed Sheeran”)); } /* * 列表循环 / public void listenToMusicByLoop() { IIterator iterator = songList.iterator(); while (iterator.hasNext()) { System.out.println(“听歌:” + ((ISong)iterator.next()).getSongInfo()); } }}/* * 容器接口 /interface IList2 { IIterator iterator(); void add(Object object); Object get(int index); int size();}/* * 歌单 /class SongList2 implements IList2 { private ISong[] songs; private int index; private int size; public SongList2(int size) { songs = new ISong[size]; index = 0; size = 0; } @Override public IIterator iterator() { return new IteratorImpl(this); } @Override public void add(Object object) { songs[index++] = (ISong) object; size ++; } @Override public Object get(int index) { if (index < size) { return songs[index]; } return null; } @Override public int size() { return size; }}/* * 迭代器 /interface IIterator { Object next(); boolean hasNext();}/* * 迭代器实现类 */class IteratorImpl implements IIterator { private IList2 list; private int index; public IteratorImpl(IList2 list) { this.list = list; this.index = 0; } @Override public Object next() { return list.get(index++); } @Override public boolean hasNext() { if (index < list.size()) { return true; } return false; }}打印结果:听歌:让我留在你身边–陈奕迅听歌:你曾是少年–SHE听歌:Perfect–Ed Sheeran代码中我们自定义了一个迭代器接口 IIterator 和迭代器具体实现类 IteratorImpl,有关键的 2 个方法,hasNext() 判断是否有存在下一个元素,next() 获取下一个元素。而 IList2 接口则比 IList1 接口多了一个获取迭代器的方法 iterator(),这让网易云音乐在遍历歌单的时候,不用直接使用 songList 来遍历,而可以通过 songList.iterator() 获取迭代器来实现遍历的过程。NetEaseMusic2.listenToMusicByLoop() 这个方法里面就直接获取迭代器来遍历了。代码:Iterator Pattern总结迭代器模式是所有设计模式中使用最广泛的,有不少开发同学知道迭代器,但是不知道它是设计模式的。虽然迭代器的代码会比没有迭代器的代码复杂,但是加上迭代器可以让容器有统一的遍历代码风格,不用各自去实现遍历方法,有更好的封装性。在 Java 中,迭代器已经运用很广泛,比如 Java 中访问 MySQL 获取数据就是用迭代器来遍历数据的。好了,迭代器模式就讲到这,大家知道的知识就不多说啦。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:行为型模式:策略模式行为型模式:责任链模式行为型模式:命令模式希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 28, 2019 · 3 min · jiezi

Proactor 与 Reactor

Reactor和Proactor对比以及优缺点两种高效的事件处理模型:Reactor模式和Proactor模式在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。在比较这两个模式之前,我们首先的搞明白几个概念,什么是阻塞和非阻塞,什么是同步和异步?同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)。阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。一般来说I/O模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞IO同步阻塞IO:在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!同步非阻塞IO:在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。异步阻塞IO:此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!异步非阻塞IO:在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。搞清楚了以上概念以后,我们再回过头来看看,Reactor模式和Proactor模式。(其实阻塞与非阻塞都可以理解为同步范畴下才有的概念,对于异步,就不会再去分阻塞非阻塞。对于用户进程,接到异步通知后,就直接操作进程用户态空间里的数据好了。)首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。我们分别以读操作和写操作为例来看看Reactor中的具体步骤:读取操作:应用程序注册读就绪事件和相关联的事件处理器事件分离器等待事件的发生当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理写入操作类似于读取操作,只不过第一步注册的是写就绪事件。下面我们来看看Proactor模式中读取操作和写入操作的过程:读取操作:应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。事件分离器等待读取操作完成事件在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。说到阻塞,首先得说说I/O等待。I/O等待是不可避免的,那么既然有了等待,就会有阻塞,但是注意,我们说的阻塞是指当前发起I/O操作的进程被阻塞。同步阻塞I/O便是指,当进程调用某些涉及I/O操作的系统调用或库函数时,比如accept()(注意accept也算在了i/o操作)、send()、recv()等,进程便暂停下来,等待I/O操作完成再继续运行。这是一种简单而有效的I/O模型,它可以和多进程结合起来有效的利用CPU资源,但是代价就是多进程的大量内存开销。同步阻塞:进程坐水,就不能烧粥 同步非阻塞:类似于用一个进程坐水,烧粥。while(true){if… if… } 好处就是一个进程处理多个i/o请求,劣势就是需要不停的轮询。区别在于等不等待数据就绪。因为数据占了等待的80%时间。同步非阻塞的优势就是一个进程里同时处理多个I/O操作。在同步阻塞I/O中,进程实际上等待的时间可能包括两部分,一个是等待数据的就绪,另一个是等待数据的复制,对于网络I/O来说,前者的时间可能要更长一些。与此不同的是,同步非阻塞I/O的调用不会等待数据的就绪,如果数据不可读或者不可写,它会立即返回告诉进程。比如我们使用非阻塞recv()接收网络数据的时候,如果网卡缓冲区中没有可接收的数据,函数就及时返回,告诉进程没有数据可读了。相比于阻塞I/O,这种非阻塞I/O结合反复的轮询来尝试数据是否就绪,防止进程被阻塞,最大的好处便在于可以在一个进程里同时处理多个I/O操作。但正是由于需要进程执行多次的轮询来查看数据是否就绪,这花费了大量的CPU时间,使得进程处于忙碌等待状态。非阻塞I/O一般只针对网络I/O有效,我们只要在socket的选项设置中使用O_NONBLOCK即可,这样对于该socket的send()或recv()便采用非阻塞方式。如果服务器想要同时接收多个TCP连接的数据,就必须轮流对每个socket调用接收数据的方法,比如recv()。不管这些socket有没有可以接收的数据,都要询问一遍,假如大部分socket并没有数据可以接收,那么进程便会浪费很多CPU时间用于检查这些socket,这显然不是我们所希望看到的。同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写二个阶段,同步的读写必须阻塞),异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。(等待”通知”)多数情况下,Web服务器对这些请求采用基于队列的自由竞争,通过多执行流(多进程或多线程)来充分占 用CPU以及I/O资源,减少任何无辜的等待时间,这其中包括了很多种具体实现的并发策略,在实际应用中,特别是Web服务器,同时处理大量的文件描述符是必不可少的。多路I/O就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程(比如电子屏,会闻到各个饭馆做好饭菜的味道)通过一种方法来同时监视所有文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。回到买面条的故事中,假如你不止买了一份面条,还在其它几个小吃店买了饺子、粥、馅饼等,因为一起逛街的朋友看到你的面条后也饿了。这些东西都需要时间来等待制作。在同步非阻塞I/O模型中,你要轮流不停的去各个小吃店询问进度,痛苦不堪。现在引入多路I/O就绪通知后,小吃城管理处给大厅安装了一块电子屏幕,以后所有小吃店的食物做好后,都会显示在屏幕上,这可真是个好消息,你只需要间隔性的看看大屏幕就可以了,也许你还可以同时逛逛附近的商店,在不远处也可以看到大屏幕。多路就绪:1、强调多路; 2、只针对请求数据是否就绪,不针对I/O读写。epoll针对的是这样的场景。select, epoll都只需要进程(我)被动接收到数据就绪(面条)”通知”,符合异步的定义。 不需要一直在饭馆等(同步阻塞),或轮询(同步非阻塞)。

February 28, 2019 · 1 min · jiezi

前端常用设计模式(2)--策略模式(strategy)

对不太了解前端的同学来说,可能JS最大的两个用途就是:1)让元素飞来飞去~2)对表单进行校验…这虽然是个玩笑,但是这两个“主要”用途的背后使用的设计模式就是策略模式。在介绍什么是策略模式之前,我们先来一起实现一个简单的React动画组件来完成“让元素飞来飞去~”的需求。一.设计一个动画组件在浏览器中实现动画,无外乎两种方式。1)利用CSS3提供的动画属性。2)利用JS脚本,每过一段时间改变元素的样式或属性。我们先来简述一下两种方式的优缺点。使用CSS3来完成动画最大的优点就是运行效率要比使用JS的方式高,因为浏览器原生支持CSS,省去了JS脚本解释时间,并且有些浏览器(比如Chrome)还提供GPU加速,进一步提高渲染性能。但是CSS3动画的缺点也是显而易见的,CSS3的动画只能描述“某一时间内,某个样式属性按照某种节奏发生变化”,也就是说CSS3提供的动画都是基于时间和运动曲线来定义的。这样定义动画有两个主要缺点:一是无法中途暂停动画:比如我有一个按钮,当hover的时候开始执行动画,在动画执行过程中鼠标移出了按钮,此时原本希望动画终止,但实际效果并不是这样。另外一个缺点是调试动画十分困难,当动画的transition-duration设置的很短的时候,往往动画总是一闪而过,捕捉不到中间状态,需要靠肉眼去一遍一遍验证效果,十分痛苦。当然CSS3动画虽然有些缺点,但依靠其优秀的性能,在制作一些简单动画的时候还是大有用途的。使用JS来实现动画的原理大体上就像制作动画片,其最大的优点就是灵活,不仅可以控制渲染的时间间隔,还可以控制每帧元素的各种属性,甚至是样式之外的属性(比如节点数据)。当然脚本动画也是有缺点的,浏览器解释脚本要占用大量的元算资源,如果处理不当,会出现动画卡顿甚至影响整个页面的交互。灵活和性能二者不可兼得,如何取舍还需要开发者在实际需求中反复推敲,使用最合适的实现方式。下面我们来使用JS的方式来实现一个简单的react动画组件。首先设计API,react组件的API就是props,假设我们动画组件叫Animate,和CSS3动画一样,我们需要指定动画的开始时间,持续时长,初始值,结束值和需要使用哪个缓动函数,最后我们打算使用流行的子组件函数(PR)的模式来给其他组件添加动画。用起来大概就是下面这个样子:<Animate startTime={+new Date()} // 开始时间 startPos={-100} // 初始值 endPos={300} // 结束位置 duration={1000} // 持续时间 easing=“linear” // 缓动函数名称> {({ position }) => { const style = { position: “absolute”, top: “200px”, right: ${position}px }; return <h2 style={style}>Hello World!</h2>; }}</Animate>有的同学对子组件函数的模式不太了解,简单介绍一下,Animate的子组件(props.children)不是组件而是一个函数,它的返回值是真正想渲染的组件(h2),类型render方法,这样设计的好处是,Animate组件可以将想传递的数据通过参数(position)的方法是传入函数中,而不是直接绑定到想渲染的组件上。比如这里的position实际上是动画每一帧计算的结果值,但是Animate组件并不知道这个值要作用在哪个样式属性(也无需知道),如何使用position在函数中决定,比如这里我们设置position去改变h2的right属性,使得Animate组件和h2组件完全解耦,增强灵活性和复用度。搞定API就完成了一半开发,剩下就是组件的实现。闲言碎语不要讲,直接上代码:class Animate extends React.Component { state = { position: 0 // 位置 }; componentDidMount() { this.startTimer(); } componentWillUnmount() { this.clearTimer(); } // 启动定时器 startTimer = () => (this.timer = setInterval(this.step, 19)); // 情况定时器 clearTimer = () => clearInterval(this.timer); // 计算每一帧 step = () => { const { startTime, duration, startPos, endPos, easing } = this.props; const nowTime = +new Date(); // 当前时间 // 判断动画是否结束,如果结束修正位置 if (nowTime >= startTime + duration) { return this.setState({ position: endPos }); // 更新位置; } const position = tween[easing]( nowTime - startTime, startPos, endPos - startPos, duration ); this.setState({ position }); }; render() { const { children = () => {} } = this.props; const { position } = this.state; return <div>{children({ position })}</div>; }}我们用一个state记录每帧计算结果position,用setInterval去实现动画刷新,在didMount的时候创建定时器(startTimer),willUnmount时候记得销毁定时器(clearTimer),step函数的作用是每次定时器调用时计算position并保存到state中,最终在render函数中将position传递到子组建函数(children)中,一个动画组件就完成了,怎么样是不是很简单?细心的同学发现了,在step里,position的值是通过const position = tween[easing]( nowTime - startTime, startPos, endPos - startPos, duration);这段代码生成的,easing是通过props传递进来的一个缓动函数的名称(如linear),它是如何转变成一个position值的呢?很简单,我们定义了一个tween对象,对象的key就是这些缓动函数的名称,而每个key对应的value就是缓动函数实现。const tween = { linear(t, b, c, d) { return c * t / d + b;},easeIn(t, b, c, d) { return c * (t /= d) * t + b;},strongEaseIn(t, b, c, d) { return c * (t /= d) * t * t * t * t + b;},strongEaseOut(t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b;},sineaseIn(t, b, c, d) { return c * (t /= d) * t * t + b;},……}注意,这些缓动函数为了保证可以被等效替换,需要用相同参数和返回值:// t:已消耗的时间,b:原始位置,c:结束位置,d: 持续总时间// 返回当前时间位置这样,这个动画组件的实现就全部介绍完了,看一下效果:很好,Hello World已经可以按各种姿势飘来飘去了~~二.再谈策略模式如果想在Animate组件中添加新的动画效果,只需要修改tween对象,而无需修改Animate组件本身,之所以可以这样,是因为我们在tween对象中定义所有缓动函数都可以接收相同的的参数,并返回相同的结果,这些缓动函数可以被等效替换。将一系列算法封装起来,是它们可以互相替换。这种设计模式就是策略模式。策略模式的主要目的是封装算法,注意这里的算法也可以延伸成函数或者规则,比如表单校验规则,只要这些算法的目的一致即可。一开始提到的JS的两大主要功能恰恰就是策略模式的最佳使用场合。这里我们就不在举例表单验证如何实现了,留给大家自己思考一下。使用策略模式可以有效避免多重条件选择语句,增强代码可读性,同时我们对算法进行了封装,易于拓展并增强了可复用度,有利就有弊,策略模式需要对算法进行抽象,整理出一系列可替换的算法,增加了代码设计难度,另外使用前需要对所有算法有所了解,违反了最少知识原则。但这些缺点相比优点还是可以接收的。三.使用策略模式管理react对话框最后我们再给出一个策略模式在项目的应用,希望可以给大家启发。在一个前端项目中不可避免的要使用对话框,特别是中后台项目。通常我们的对话框组件用起来都是这样的(以antd为例):import { Modal, Button } from ‘antd’;class App extends React.Component { state = { visible: false } showModal = () => { this.setState({ visible: true, }); } handleOk = (e) => { console.log(e); this.setState({ visible: false, }); } handleCancel = (e) => { console.log(e); this.setState({ visible: false, }); } render() { return ( <div> <Button type=“primary” onClick={this.showModal}>Open</Button> <Modal title=“Basic Modal” visible={this.state.visible} onOk={this.handleOk} onCancel={this.handleCancel} > <p>Some contents…</p> <p>Some contents…</p> <p>Some contents…</p> </Modal> </div> ); }}ReactDOM.render(<App />, mountNode); 我们需要定义一个visible状态去控制对话框展示与否,还需要定义showModal的打开对话框,还有定义handleOK和handleCancel等回调函数去处理对话框交互,当一个页面存在N个不同的对话框时,以上工作将翻N倍,并且很容易出错。那有什么办法解决这个问题呢?我们想一下,对话框本身存在互斥的特性,一般不允许同时打开两个对话框,所以我们可以使用策略模式加单例模式来制作一个全局的对话框组件,让它只有一个visible状态控制展示,具体要展示哪个对话框通过参数传递进去,就像我们之前传递缓动函数一样。下面是对话框管理组件的代码实现:import React from ‘react’import MODAL_MAP from ‘./modalMap’import { simpleCloneObject as clone } from ‘@/js/utils’const DEFAULT_STATE = { name: ‘’, // 弹窗名称,从map中查找 visible: false, // 弹窗是否可见 onOk: () => { }, // 确定回调 onCancel: () => { }, // 关闭回调 extProps: {} // 透传属性}class Manager extends React.Component { state = clone(DEFAULT_STATE) componentDidMount() { this.props.onRef(this) // 引用 } // 打开弹窗 open = ({ name, onOk, onCancel, …extProps }) => this.setState({ name, onOk, onCancel, extProps, visible: true }) // 关闭弹窗 onOk = () => { const { onOk } = this.state onOk && onOk() this.setState(clone(DEFAULT_STATE)) } onCancel = () => { const { onCancel } = this.state onCancel && onCancel() this.setState(clone(DEFAULT_STATE)) } render() { const { name, visible, extProps } = this.state const Modal = MODAL_MAP[name] return Modal ? <Modal visible={visible} {…extProps} onOk={this.onOk} onCancel={this.onCancel} /> : null }}export default Manager 可以看到最后要渲染的Modal是已通过name从MODAL_MAP对象中获取的,全部对话框共享这个组件唯一状态和方法。那么问题就来了,visible属性变成了这个组件内部的一个state,而对话框的打开往往是在组件外部决定的,我们怎么能让外部组件访问到这个组件内部的state呢?很简单,代码如下:import React from ‘react’import ReactDOM from ‘react-dom’import Manager from ‘./Manager’const init = () => { let manager // 实例引用 const onRef = ref => manager = ref const dom = document.createElement(‘div’) dom.id = ‘modal-manager’ document.querySelector(‘body’).appendChild(dom) ReactDOM.render(<Manager onRef={onRef} />, dom) return manager}export default init() 我们设计了一个init函数,在函数动态创建了一个dom节点,并将之前的公共对话框组件渲染到这个节点上,同时利用ref属性和闭包返回了这个组件的实例,通过实例就可以访问组件内部的属性和方法了。最后看看如何应用我们这个对话框管理器,只需要两步,再也不需要多余的状态和方法,是不是清爽很多?四.函数就是策略谷歌的计算机学家Peter Norvig曾在一次演讲中说过,“在函数作为一等公民的语言中,策略模式是隐形存在的。” 显然JS就满足这样的条件,因此我认为策略模式是最能体现JS特点设计模式之一。我们上面实现动画组件和对话框管理组件时专门定义了两个对象用来存放缓动函数和对话框,其实完全可以直接把函数或组件作为参数传递进去,而不是通过Key去对象中取值。这样做可以进一步提高灵活度,但是降低了代码的健壮性(因为不能确定传入的值是否符合标准),这之间的取舍就对开发人员的架构能力提出了挑战。最后附上文章里的代码地址:https://codesandbox.io/s/myl7j02p1x如有问题欢迎留言讨论,谢谢大家[emoji] ...

February 26, 2019 · 3 min · jiezi

行为型模式:策略模式

LieBrother原文:行为型模式:策略模式十一大行为型模式之五:策略模式。简介姓名 :策略模式英文名 :Strategy Pattern价值观 :集计谋于一身个人介绍 :Define a family of algorithms,encapsulate each one,and make them interchangeable.定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。(来自《设计模式之禅》)你要的故事先看一张拍得不好看的图片每天上完班回到家第一件事情是干啥?有人一进门就躺在沙发上闭目养神、有人一进门躺在沙发上玩手机、有人一进门就陪自己的小宠物玩等等。而我进门第一件事就是洗澡,洗完澡很容易就把一整天的疲惫感给消除掉,然后就可以开始美好的下班时光。现实没那么美好,洗完澡后还要洗衣服,大学手洗了 4 年的衣服,一出来工作,宿舍第一必需品就是洗衣机。细看洗衣机,有很多种洗衣类型,比如:标准、大物、快洗、轻柔。洗衣类型的区别在于洗衣服的过程不一样,洗衣过程包括有浸泡、洗涤、漂洗、脱水,还有洗衣服的时间也不一样。细想可以发现这 4 种洗衣类型其实是洗衣服的 4 种不同的策略,也即是 4 种不同的算法。根据这个思路,我们可以用代码实现它,定义一个接口 WashingStrategy 定义洗衣服类型,而这些类型都有各自的洗衣过程,比如标准洗衣类型就包括浸泡、洗涤、漂洗、脱水,而快洗则只包括洗涤、漂洗、脱水。而我们洗衣服则需要选择某个洗衣类型后,洗衣机就开始工作了。过程如下代码所示。public class StrategyTest { public static void main(String[] args) { WashingStrategy washingStrategy = new StandardWashingStrategy(); WashingMachine washingMachine = new WashingMachine(washingStrategy); washingMachine.washingClothes(); }}/** * 洗衣类型 /interface WashingStrategy { void washing();}/* * 洗衣机 /class WashingMachine { private WashingStrategy washingStrategy; public WashingMachine(WashingStrategy washingStrategy) { this.washingStrategy = washingStrategy; } public void washingClothes() { this.washingStrategy.washing(); }}/* * 标准 /class StandardWashingStrategy implements WashingStrategy{ @Override public void washing() { System.out.println(“标准流程:”); System.out.println("[浸泡] 10 分钟"); System.out.println("[洗涤] 2 次,每次 15 分钟"); System.out.println("[漂洗] 1 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println(“总共耗时:55 分钟”); }}/* * 快洗 /class QuickWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println(“快洗流程:”); System.out.println("[洗涤] 1 次,每次 10 分钟"); System.out.println("[漂洗] 1 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println(“总共耗时:25 分钟”); }}/* * 大物 /class BigClothesWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println(“大物流程:”); System.out.println("[浸泡] 30 分钟"); System.out.println("[洗涤] 3 次,每次 15 分钟"); System.out.println("[漂洗] 2 次,每次 10 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println(“总共耗时:100 分钟”); }}/* * 轻柔 /class SoftWashingStrategy implements WashingStrategy { @Override public void washing() { System.out.println(“轻柔流程:”); System.out.println("[浸泡] 10 分钟"); System.out.println("[漂洗] 2 次,每次 15 分钟"); System.out.println("[脱水] 5 分钟"); System.out.println(“总共耗时:45 分钟”); }}标准流程:[浸泡] 10 分钟[洗涤] 2 次,每次 15 分钟[漂洗] 1 次,每次 10 分钟[脱水] 5 分钟总共耗时:55 分钟是不是感觉策略模式很简单呢?上面代码就是完整的策略模式示例,是不是感觉有些问题,这 4 种洗衣类型对象完全暴露给了用户,这也是策略模式的缺点。往往策略模式不会单独使用,会和其他设计模式一起使用,比如和简单工厂模式一起使用就可以解决这个对外暴露对象的问题,看下面代码。/* * 洗衣类型选择 */class WashingFactory { public static WashingStrategy getWashingStrategy(String type) { if (“Quick”.equals(type)) { return new QuickWashingStrategy(); } if (“BigClothes”.equals(type)) { return new BigClothesWashingStrategy(); } if (“Soft”.equals(type)) { return new SoftWashingStrategy(); } return new StandardWashingStrategy(); }}public class StrategyTest { public static void main(String[] args) { WashingStrategy washingStrategy2 = WashingFactory.getWashingStrategy(“Soft”); WashingMachine washingMachine2 = new WashingMachine(washingStrategy2); washingMachine2.washingClothes(); }}打印结果:轻柔流程:[浸泡] 10 分钟[漂洗] 2 次,每次 15 分钟[脱水] 5 分钟总共耗时:45 分钟代码中使用 WashingFactory 来封装 4 种策略,使得策略没有对外暴露,我们也了解到设计模式之间具有互补的关系,有些时候并不是单独存在的。代码:Strategy Pattern总结策略模式是一个很好的封装各种复杂处理的设计模式,让使用者根据自己的选择来选中策略,而不用修改其他代码。当策略太多的时候,可能造成使用方变得复杂、难管理多个策略的问题,利用工厂方法模式可以很好的解决这个难题。这其中也是一个见招拆招的问题,设计模式在真正运用中也是这样子的,遇到问题使用恰当的设计模式去解决问题。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 26, 2019 · 2 min · jiezi

前端常用设计模式(1)--装饰器(decorator)

一.引子-先来安利一款好游戏《塞尔达传说-荒野之息》,这款于2017年3月3日由任天堂(“民间高手”)发售在自家主机平台WIIU和SWITCH上的单机RPG游戏,可谓是跨时代的“神作”了。第一次制作“开放类”游戏的任天堂就教科书般的定义了这类游戏应该如何制作。而这个游戏真正吸引我的地方是他的细节,举个栗子,《荒野之息》中的世界有天气和温度两个概念,会下雨打雷,有严寒酷暑,但是这些天气不想大多数游戏一样,只是简单的背景,而是实实在在会影响主角林克(Link)每一个操作。比如,下雨天去爬山会打滑;打雷天如果身上有金属装备会被雷劈(木制装备则没事!);严寒中会慢慢流失体力(穿上一件保暖衣就解决了);酷暑中使用爆炸箭则会原地爆炸!等等;就是这些细节让这个游戏世界显的无比真实又有趣。二.问题-如何设计这样的游戏代码?作为程序猿,玩游戏之余不禁会思考,这样的游戏代码应该如何设计编写?比如“攀爬”这个动作,需要判断攀爬的位置,林克的装备(有些装备能让你爬的更快),当时的天气,林克的体力等等众多条件,里面肯定参杂的无数if else,更何况这只是其中一个简单的操作,拓展到全部游戏,其复杂的不可想象。显然这样的设计是不行的。那我们假设“攀爬”的方法只专心处理攀爬这件事(有体力就能成功,反之失败),其他判断在方法外部执行,比如判断天气,装备,位置等等,这样就符合了程序设计的单一职责和低耦合等原则,并且判断天气的方法还可以拿去别的地方复用,增强了代码的复用度和可测试度,似乎可行!那应该如何设计这样的代码呢?这就引出了我们今天的主角-装饰器模式。三.主角-装饰器模式(decorator)根据GoF在《设计模式:可复用面向对象软件的基础》(以下简称《设计模式》)一书中对装饰器模式定义:装饰器模式又称包装模式(“wrapper”),目的是以对用户透明的方式扩展对象的功能,是继承的一种代替方案。一起划重点:对用户透明:一般指被装饰过的对象的对外接口不变,“攀爬”被怎么装饰都还是“攀爬”。扩展对象的功能:一般指修改或添加对象功能,比如林克在雪地就可以用盾牌滑雪,平地则没有这个能力。继承的一种代替方案:熟悉面向对象的同学一定对继承并不陌生,这里我们重点谈谈继承本身的一些缺点:1)继承中子类和超类存在强耦合性,超类的修改会影响全部子类;2)超类对子类是“白盒复用”,子类必须了解超类的全部实现,破坏了封装性。3)当项目庞大时,继承会使得子类爆发性增长,比如《荒野之息》中存在料理系统,任意两种食材均可以搭配出一款料理,假定有10中可以使用食材,使用继承的方式就要构建10*10=100个子类表示料理结果,而装饰器模式仅仅使用10+1=11个子类就可以完成以上工作。(还包括了任意种食材的混合,事实上游戏中的确可以。)最后,总结一下装饰器模式的特点:不改变对象自身的基础上,在程序运行时给对象添加某种功能,一句话:锦上添花。(想想《王者荣耀》中最赚钱的皮肤,怎么全是游戏,喂!)四.场景-面向切片编程(AOP)说到装饰器,最经典的应用场景就是面向切片编程(Aspect Oriented Programming,以下简称AOP),AOP适合某些具有横向逻辑(可切片)的应用,比如提交表单,点击提交按钮以后执行的逻辑是:上报点击 -> 校验数据 -> 提交数据 -> 上报结果 。可以看到,首尾的上报日志功能和核心业务逻辑并没有直接关系,并且几乎所有表单提交都需要上报日志的功能,因此,上报日志,这个功能就可以单独抽象出来,最后在程序运行(或编译)时动态织入业务逻辑中。类似的功能还有:数据校验,权限控制,异常处理,缓存管理等等。AOP的优点是可以保持业务逻辑模块的纯净和高内聚,同时方便功能复用,通过装饰器就可以很方便的把功能模块装饰到主业务逻辑中去。五.应用-前端开发中的应用接下来我们一起看看具体装饰器模式是如何在前端开发中应用的。Talk is cheap, show me the code! (屁话少说,放码过来!)在JS中改变一个对象再简单不过了。得力于JS是一门基于原型的弱类型语言,给对象添加或修改功能都十分容易,因此传统的面向对象中的装饰器模式在JS中的应用并不太多(ES6正式提出class以后场景有所增加)。我们先简单模拟一下面向对象中的装饰器模式。假设我们要开发一个飞机大战的游戏,飞机可以切换装备的武器,发射不同的子弹。我们先实现一个飞机的类,并实现一个fire方法。接着,我们实现一个发射导弹的装饰器类这个类接收一个飞机实例,并且重新实现了fire方法,在方法内部先调用原来实例的fire方法,接着扩展此方法,增加了发射导弹的功能。类似的我们再实现一个发射原子弹的装饰器。最后我们看一下应该如何使用这两个装饰器。可以看到,经过两个装饰器装饰后的plane实例,再调用fire方法时,就可以同时发射三种子弹了。而装饰器本身并没有直接改写Plane类,只是增强了它的fire方法,对plane实例的使用者也是透明的。接下来我们看一看如何应用装饰器在JS中实现AOP编程。首先我们扩展一下函数的原型,让每个函数都可以被装饰。我们给函数增加一个before和after方法,这两个方法各自接收一个新的函数,并保证新函数在原函数之前(before)或之后(after)执行。这里需要注意的是新函数和原函数具有相同this和参数。有了两个方法,以前很多复杂的需求就变得很简单了。栗子一:挂载多个onload函数通常情况下,window.onload只能挂载一个回调函数,重复声明回调函数,后面的会把之前声明的覆盖掉,有了after以后,这个麻烦解决了。栗子二:日志上报栗子三:追加(改变)参数比如,为了增加安全性,给所有接口都增加一个token参数,如果不实用AOP,我们只能改ajax方法了。但是有了AOP,就可以像下面这样操作。原理就是before函数和原函数接收相同的this和参数,并且before会在原函数之前执行。其实AOP在前端项目中的应用场景还很多,比如校验表单参数,异常处理,数据缓存,本地持久化等,这里不在一一举例了。有些同学对直接改写函数的原型比较抵触,这里我们也给出函数式的before实现。六.ES7-@decorator语法在JS未来的标准(ES7)中,装饰器也已被加入到了提案中。前端同学都知道jQuery最大的特点就是它链式调用的API设计,其核心是每个方法都返回this,也就是jQuery对象实例,我们不妨先实现一个高阶函数,用于实现链式调用。fluent函数接收一个函数fn作为参数,返回一个新的函数,在新函数内部通过apply调用fn,并最终返回上下文this。有了这个函数,我们就可以很方便的给任意对象的方法添加链式调用。接下来,我们看看如何使用ES7的@decorator语法来简化上面的代码,先来看一下结果。熟悉JAVA的同学一眼就看出这不是注解写法么,没错,ES7中的@decorator正是参考了Python和JAVA语法设计出来的。@后面的fluentDecorate是一个装饰器函数,这个函数接收三个参数,分别是target,name和descriptor,这三个参数和Object.defineProperty方法的参数完全相同,实际上@decorator也正是这个方法的语法糖而已。值得注意的是@decorator不止可以作用在对象或类的方法上面,还可以直接作用在类(class)上,区别是装饰函数的第一个参数target不同,当作用在方法上时,target指向对象本身,而当作用在类时target指向类(class),并且name和descriptor都是undefined。以下给出fluentDecorate函数的完整实现。通常我们可以把这个装饰函数再抽象一下,让他成为一个高阶函数,可以接收我们最开始定义的fluent函数或者其他函数(比如截流函数等),然后返回一个用这个函数装饰的新装饰函数,更具有通用型。@decorator到目前为止还只是个提案,没有任何浏览器支持了这个语法,但是好在可以使用Babel以插件(transform-decorators-legacy)的形式在自己的项目中体验。注意,@decorator只能作用于类和类的方法上,不能用于普通函数,因为函数存在变量提升,而类是不会提升的。七.组件-装饰器在React项目中的应用最后结合目前前端最火的框架React,来看看装饰器是如何在组件上使用的。回到最开始的假设,如何开发出《荒野之息》这样细节丰富的游戏,下面我们就使用React搭配装饰器来模拟一下游戏中的细节实现。我们先实现一个Person组件,用来代指游戏的主角,这个组件可以接收名字,生命值,攻击类等初始化参数,并在一个卡片中展示这些参数,当生命值为0时,会提示“游戏结束”。并且在卡片中放置一个“JUMP”按钮,用点击按钮模拟主角跳跃的交互。组件调用:实现结果如下,是不是很抽象?哈哈!接下来我们想要模拟游戏中的天气和温度变化,需要实现一个“自然环境”的组件Natural,这个组件自身有天气(wat)和温度(tep)两个状态(state),并且可以通过输入改变这两个状态,我们之前创建的Person组件作为后代插入这个组件中,并且接收Natural的wat和tep状态作为属性。好了,我们的实验页面就完成了,最终效果如下,上面可以通过进度条和单选按钮改变天气和温度,改变后的结果通过props传递给游戏主角。但是现在改变温度和天气对主角并不会造成任何影响,接下来我们想在不改变原有Person组件的前提下,实现两个功能:第一,当温度大于50度或者小于10度的时候,主角生命值慢慢下降;第二当天气是雨天的时候,主角每跳跃3次就失败1次。先来实现第一个功能,温度过高和过低时,主角生命值慢慢减少。我们的思路是实现一个装饰器,用这个装饰器在外部装饰Person组件,使得这个组件可以感知温度变化。先给出实现:仔细观察decorateTep函数,它接收一个组件(A)作为参数,返回一个新的React组件(B),在B内部维护了一个hp和tep状态 ,在tep处于临界值时,改变B的hp,最后render时用B的hp代替原来的hp属性传递给A组件。这不是就是高阶组件(HOC)么?!没错,当装饰器去装饰一个组件时,它的实现和高阶组件完全一致。通过返回一个新组件的方式去增强原有组件的能力,这也符合React提倡的组件组合的设计模式(注意不是mixin或者继承),decorateTep的使用方法很简单,一行代码搞定:接下来我们来实现第二个功能,下雨时跳跃会偶尔失败,这里我们换一个策略,不再装饰Person组件,而是装饰组件内部的onJump跳跃方法。代码如下:区别之前的decorateTep,这个decorateWat装饰器的重点是第三个参数descriptor,之前提到,descriptor参数是被装饰方法的描述对象,它的value属性指向的就是原方法(onJump),这里我们用变量method保存原方法,同时使用i记录点击次数,通过闭包延长这两个变量的生命周期,最后实现一个新的方法代替原方法,在新方法内部通过apply调用原方法并重置变量i,注意decorateWat最后返回的是改变以后的descriptor对象。经过装饰器装饰过的onJump方法如下:好了,接下来就是见证奇迹的时刻!八.轮子-常用装饰器库事实上现在已经有很多开源装饰器的库可以拿来使用,以下是质量较好的轮子,希望可以给大家提供帮助。core-decoratorslodash-decoratorsreact-decoration九.参考-相关资料阅读全部演示源代码五分钟让你明白为什么塞尔达可以夺得年度游戏《荒野之息》中46个精彩的小细节日亚上一位玩家对《荒野之息》的评价面向切片编程《JavaScript 设计模式与开发实践》曾探;人民邮电出版社《JavaScript 高级程序设计(第三版)》Zakas;人民邮电出版社《ES 6 标准入门(第二版)》阮一峰;电子工业出版社最后,如有不对的地方,欢迎各位小伙伴留言拍砖,你们的支持是我继续的最大动力!谢谢大家!

February 25, 2019 · 1 min · jiezi

基于泛型编程的序列化实现方法

写在前面序列化是一个转储-恢复的操作过程,即支持将一个对象转储到临时缓冲或者永久文件中和恢复临时缓冲或者永久文件中的内容到一个对象中等操作,其目的是可以在不同的应用程序之间共享和传输数据,以达到跨应用程序、跨语言和跨平台的解耦,以及当应用程序在客户现场发生异常或者崩溃时可以即时保存数据结构各内容的值到文件中,并在发回给开发者时再恢复数据结构各内容的值以协助分析和定位原因。泛型编程是一个对具有相同功能的不同类型的抽象实现过程,比如STL的源码实现,其支持在编译期由编译器自动推导具体类型并生成实现代码,同时依据具体类型的特定性质或者优化需要支持使用特化或者偏特化及模板元编程等特性进行具体实现。Hello World#include <iostream>int main(int argc, char* argv[]){ std::cout << “Hello World!” << std::endl; return 0;}泛型编程其实就在我们身边,我们经常使用的std和stl命名空间中的函数和类很多都是泛型编程实现的,如上述代码中的std::cout即是模板类std::basic_ostream的一种特化namespace std{ typedef basic_ostream<char> ostream;}从C++的标准输入输出开始除了上述提到的std::cout和std::basic_ostream外,C++还提供了各种形式的输入输出模板类,如std::basic_istream, std::basic_ifstream,std::basic_ofstream, std::basic_istringstream,std::basic_ostringstream等等,其主要实现了内建类型(built-in)的输入输出接口,比如对于Hello World可直接使用于字符串,然而对于自定义类型的输入输出,则需要重载实现操作符>>和<<,如对于下面的自定义类class MyClip{ bool mValid; int mIn; int mOut; std::string mFilePath;};如使用下面的方式则会出现一连串的编译错误MyClip clip;std::cout << clip;错误内容大致都是一些clip不支持<<操作符并在尝试将clip转为cout支持的一系列的内建类型如void*和int等等类型时转换操作不支持等信息。为了解决编译错误,我们则需要将类MyClip支持输入输出操作符>>和<<,类似实现代码如下inline std::istream& operator>>(std::istream& st, MyClip& clip){ st >> clip.mValid; st >> clip.mIn >> clip.mOut; st >> clip.mFilePath; return st;}inline std::ostream& operator<<(std::ostream& st, MyClip const& clip){ st << clip.mValid << ’ ‘; st << clip.mIn << ’ ’ << clip.mOut << ’ ‘; st << clip.mFilePath << ’ ‘; return st;}为了能正常访问类对象的私有成员变量,我们还需要在自定义类型里面增加序列化和反序列化的友元函数(回忆一下这里为何必须使用友元函数而不能直接重载操作符>>和<<?),如friend std::istream& operator>>(std::istream& st, MyClip& clip);friend std::ostream& operator<<(std::ostream& st, MyClip const& clip);这种序列化的实现方法是非常直观而且容易理解的,但缺陷是对于大型的项目开发中,由于自定义类型的数量较多,可能达到成千上万个甚至更多时,对于每个类型我们则需要实现2个函数,一个是序列化转储数据,另一个则是反序列化恢复数据,不仅仅增加了开发实现的代码数量,如果后期一旦对部分类的成员变量有所修改,则需要同时修改这2个函数。同时考虑到更复杂的自定义类型,比如含有继承关系和自定义类型的成员变量class MyVideo : public MyClip{ std::list<MyFilter> mFilters;};上述代码需要转储-恢复类MyVideo的对象内容时,事情会变得更复杂些,因为还需要转储-恢复基类,同时成员变量使用了STL模板容器list与自定义类’MyFilter`的结合,这种情况也需要自己去定义转储-恢复的实现方式。针对以上疑问,有没有一种方法能减少我们代码修改的工作量,同时又易于理解和维护呢?Boost序列化库对于使用C++标准输入输出的方法遇到的问题,好在Boost提供了一种良好的解决方式,则是将所有类型的转储-恢复操作抽象到一个函数中,易于理解,如对于上述类型,只需要将上述的2个友元函数替换为下面的一个友元函数template<typename Archive> friend void serialize(Archive&, MyClip&, unsigned int const);友元函数的实现类似下面的样子template<typename A>void serialize(A &ar, MyClip &clip, unsigned int const ver){ ar & BOOST_SERIALIZATION_NVP(clip.mValid); ar & BOOST_SERIALIZATION_NVP(clip.mIn); ar & BOOST_SERIALIZATION_NVP(clip.mOut); ar & BOOST_SERIALIZATION_NVP(clip.mFilePath);}其中BOOST_SERIALIZATION_NVP是Boost内部定义的一个宏,其主要作用是对各个变量进行打包。转储-恢复的使用则直接作用于操作符>>和<<,比如// storeMyClip clip;······std::ostringstream ostr;boost::archive::text_oarchive oa(ostr);oa << clip;// loadstd::istringstream istr(ostr.str());boost::archive::text_iarchive ia(istr);ia >> clip;这里使用的std::istringstream和std::ostringstream即是分别从字符串流中恢复数据以及将类对象的数据转储到字符串流中。对于类MyFilter和MyVideo则使用相同的方式,即分别增加一个友元模板函数serialize的实现即可,至于std::list模板类,boost已经帮我们实现了。这时我们发现,对于每一个定义的类,我们需要做的仅仅是在类内部声明一个友元模板函数,同时类外部实现这个模板函数即可,对于后期类的成员变量的修改,如增加、删除或者重命名成员变量,也仅仅是修改一个函数即可。Boost序列化库已经足够完美了,但故事并未结束!在用于端上开发时,我们发现引用Boost序列化库遇到了几个挑战端上的编译资料很少,官方对端上编译的资料基本没有,在切换不同的版本进行编译时经常会遇到各种奇怪的编译错误问题Boost在不同的C++开发标准之间兼容性不够好,尤其是使用libc++标准进行编译链接时遇到的问题较多Boost增加了端上发行包的体积Boost每次序列化都会增加序列化库及版本号等私有头信息,反序列化时再重新解析,降低了部分场景下的使用性能基于泛型编程的序列化实现方法为了解决使用Boost遇到的这些问题,我们觉得有必要重新实现序列化库,以剥离对Boost的依赖,同时能满足如下要求由于现有工程大量使用了Boost序列化库,因此兼容现有的代码以及开发者的习惯是首要目标尽量使得代码修改和重构的工作量最小兼容不同的C++开发标准提供比Boost序列化库更高的性能降低端上发行包的体积为了兼容现有使用Boost的代码以及保持当前开发者的习惯,同时使用代码修改的重构的工作量最小,我们应该保留模板函数serialize,同时对于模板函数内部的实现,为了提高效率也不需要对各成员变量重新打包,即直接使用如下定义#define BOOST_SERIALIZATION_NVP(value) value对于转储-恢复的接口调用,仍然延续目前的调用方式,只是将输入输出类修改为alivc::text_oarchive oa(ostr);alivc::text_iarchive ia(istr);好了,到此为止,序列化库对外的接口工作已经做好,剩下的就是内部的事情,应该如何重新设计和实现序列化库的内部框架才能满足要求呢?先来看一下当前的设计架构的处理流程图比如对于转储类text_oarchive,其支持的接口必须包括explicit text_oarchive(std::ostream& ost, unsigned int version = 0);template <typename T> text_oarchive& operator<<(T& v);template <typename T> text_oarchive& operator&(T& v);开发者调用操作符函数<<时,需要首先回调到相应类型的模板函数serialize中template <typename T>text_oarchive& operator<<(T& v){ serialize(*this, v, mversion); return *this;}当开始对具体类型的各个成员进行操作时,这时需要进行判断,如果此成员变量的类型已经是内建类型,则直接进行序列化,如果是自定义类型,则需要重新回调到对应类型的模板函数serialize中template <typename T>text_oarchive& operator&(T& v){ basic_save<T>::invoke(*this, v, mversion); return *this;}上述代码中的basic_save::invoke则会在编译期完成模板类型推导并选择直接对内建类型进行转储还是重新回调到成员变量对应类型的serialize函数继续重复上述过程。由于内建类型数量有限,因此这里我们选择使模板类basic_save的默认行为为回调到相应类型的serialize函数中template <typename T, bool E = false>struct basic_load_save{ template <typename A> static void invoke(A& ar, T& v, unsigned int version) { serialize(ar, v, version); }};template <typename T>struct basic_save : public basic_load_save<T, std::is_enum<T>::value>{};这时会发现上述代码的模板参数多了一个参数E,这里主要是需要对枚举类型进行特殊处理,使用偏特化的实现如下template <typename T>struct basic_load_save<T, true>{ template <typename A> static void invoke(A& ar, T& v, unsigned int version) { int tmp = v; ar & tmp; v = (T)tmp; }};到这里我们已经完成了重载操作符&的默认行为,即是不断进行回溯到相应的成员变量的类型中的模板函数serialize中,但对于碰到内建模型时,我们则需要让这个回溯过程停止,比如对于int类型template <typename T>struct basic_pod_save{ template <typename A> static void invoke(A& ar, T const& v, unsigned int) { ar.template save(v); }};template <>struct basic_save<int> : public basic_pod_save<int>{};这里对于int类型,则直接转储整数值到输出流中,此时text_oarchive则还需要增加一个终极转储函数template <typename T>void save(T const& v){ most << v << ’ ‘;}这里我们发现,在save成员函数中,我们已经将具体的成员变量的值输出到流中了。对于其它的内建类型,则使用相同的方式处理,要以参考C++ std::basic_ostream的源码实现。相应的,对于恢复操作的text_iarchive的操作流程如下图测试结果我们对使用Boost以及重新实现的序列化库进行了对比测试,其结果如下代码修改的重构的工作非常小,只需要删除Boost的相关头文件,以及将boost相关命名空间替换为alivc,BOOST_SERIALIZATION_FUNCTION以及BOOST_SERIALIZATION_NVP的宏替换Android端下的发行包体积大概减少了500KB目前的消息处理框架中,处理一次消息的平均时间由100us降低到了25us代码实现约300行,更轻量级未来还能做什么由于当前项目的原因,重新实现的序列化还没有支持转储-恢复指针所指向的内存数据,但当前的设计框架已经考虑了这种拓展性,未来会考虑支持。总结泛型编程能够大幅提高开发效率,尤其是在代码重用方面能发挥其优势,同时由于其类型推导及生成代码均在编译期完成,并不会降低性能序列化对于需要进行转储-恢复的解耦处理以及协助定位异常和崩溃的原因分析具有重要作用利用C++及模板自身的语言特性优势,结合合理的架构设计,即易于拓展又能尽量避免过度设计参考资料https://www.ibm.com/developerworks/cn/aix/library/au-boostserialization/本文作者:lifesider阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 25, 2019 · 2 min · jiezi

行为型模式:责任链模式

LieBrother原文:行为型模式:责任链模式十一大行为型模式之四:责任链模式。简介姓名 :责任链模式英文名 :Chain of Responsibility Pattern价值观 :责任归我个人介绍 :Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。(来自《设计模式之禅》)你要的故事快要金三银四了,很多同学蠢蠢欲动想要去外面看看世界,而大家有没有提前了解各大企业的面试流程呢?这里我就给大家科普一下大多数互联网企业的面试流程,正好责任链模式用得上。在互联网企业中,程序员这个岗位的招聘流程大同小异,而一般至少都会有 3 轮面试,分别是 2 轮技术面和 1 轮 HR 面。而这几轮面试都是层层递进的,最开始面试一般是组长面试,通过之后就是部门领导面试,再通过之后就是 HR 面试,HR 面试通过之后就可以成功拿到 Offer 了。故事从小明参加某公司的面试开始,某公司的招聘流程就是上面说的 3 轮面试。招聘流程的面试官分别是:第一面是组长老刚,第二面是部门经理老孙,第三面也就是终面是 HR 老刘。为什么说这个场景符合责任链模式呢?首先不管是组长还是部门经理还是 HR,他们都作为面试官,面试官赋予他们的权利是去面试来公司面试的同学,而面试的结果是可传递性的,也就是如果面试通过,就会到下一轮面试,最终成为一条传递链。我们用代码模拟这个过程。public class ChainOfResponsibilityTest { public static void main(String[] args) { Interviewee interviewee = new Interviewee(“小明”); TeamLeader teamLeader = new TeamLeader(“老刚”); DepartMentManager departMentManager = new DepartMentManager(“老孙”); HR hr = new HR(“老刘”); // 设置面试流程 teamLeader.setNextInterviewer(departMentManager); departMentManager.setNextInterviewer(hr); // 开始面试 teamLeader.handleInterview(interviewee); }}/** * 面试者 /class Interviewee { private String name; private boolean teamLeaderOpinion; private boolean departMentManagerOpinion; private boolean hrOpinion; public Interviewee(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isTeamLeaderOpinion() { return teamLeaderOpinion; } public void setTeamLeaderOpinion(boolean teamLeaderOpinion) { this.teamLeaderOpinion = teamLeaderOpinion; } public boolean isDepartMentManagerOpinion() { return departMentManagerOpinion; } public void setDepartMentManagerOpinion(boolean departMentManagerOpinion) { this.departMentManagerOpinion = departMentManagerOpinion; } public boolean isHrOpinion() { return hrOpinion; } public void setHrOpinion(boolean hrOpinion) { this.hrOpinion = hrOpinion; }}/* * 面试官 /abstract class Interviewer { protected String name; protected Interviewer nextInterviewer; public Interviewer(String name) { this.name = name; } public Interviewer setNextInterviewer(Interviewer nextInterviewer) { this.nextInterviewer = nextInterviewer; return this.nextInterviewer; } public abstract void handleInterview(Interviewee interviewee);}/* * 组长 /class TeamLeader extends Interviewer { public TeamLeader(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println(“组长[” + this.name + “]面试[” + interviewee.getName() + “]同学”); interviewee.setTeamLeaderOpinion(new Random().nextBoolean()); if (interviewee.isTeamLeaderOpinion()) { System.out.println("[" + interviewee.getName() + “]同学组长轮面试通过”); this.nextInterviewer.handleInterview(interviewee); } else { System.out.println("[" + interviewee.getName() + “]同学组长轮面试不通过”); } }}/* * 部门经理 /class DepartMentManager extends Interviewer { public DepartMentManager(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println(“部门经理[” + this.name + “]面试[” + interviewee.getName() + “]同学”); interviewee.setDepartMentManagerOpinion(new Random().nextBoolean()); if (interviewee.isDepartMentManagerOpinion()) { System.out.println("[" + interviewee.getName() + “]同学部门经理轮面试通过”); this.nextInterviewer.handleInterview(interviewee); } else { System.out.println("[" + interviewee.getName() + “]同学部门经理轮面试不通过”); } }}/* * HR */class HR extends Interviewer { public HR(String name) { super(name); } @Override public void handleInterview(Interviewee interviewee) { System.out.println(“HR[” + this.name + “]面试[” + interviewee.getName() + “]同学”); interviewee.setHrOpinion(new Random().nextBoolean()); if (interviewee.isHrOpinion()) { System.out.println("[" + interviewee.getName() + “]同学HR轮面试通过, 恭喜拿到 Offer”); } else { System.out.println("[" + interviewee.getName() + “]同学HR轮面试不通过”); } }}打印结果:组长[老刚]面试[小明]同学[小明]同学组长轮面试通过部门经理[老孙]面试[小明]同学[小明]同学部门经理轮面试通过HR[老刘]面试[小明]同学[小明]同学HR轮面试通过, 恭喜拿到 Offer上面代码打印结果是小明通过层层筛选,通过了面试,拿到了 Offer。下面的图来展现这次面试的流程。讲解一下代码,Interviewee 是面试者,对于企业来说这个面试者来面试的过程会有 3 个关键标识,就是 3 位面试官对这位面试者的评价,只有都评价好才能通过面试拿到 Offer,所以 Interviewee 类有 3 位面试官的面试结果。Interviewer 是面时官,企业中面试官不是一个职位,而是一个工作,帮企业招到合适的人才,所以它是一个抽象类,有一个抽象方法就是去面试应聘者,具体面试过程又各面试官实现,而因为这个面试会有结果反馈,结果好的会进入下一轮面试,所以会有下一个面试官的对象引用,责任链模式也就在这里体现。TeamLeader、DepartMentManager、HR 则为公司的不同职位,而这些职位当公司需要招聘时,都需要去充当面试官,所以都继承了 Interviewer。这整个过程就构成了责任链模式代码示例,希望在金三银四各位蠢蠢欲动的朋友们都能闯到最后一关拿下 HR 姐姐。代码:Chain of Responsibility Pattern总结责任链模式很好的把处理的逻辑封装起来,在代码中,我们看到的只是组长面试,但是其实背后隐藏着部门经理和HR的面试。责任链是不是看起来很熟悉?在开发 Java Web 项目的时候是不是有用到过?Filter 过滤器里面就是用责任链模式来实现的。上面代码还用到了另一个模式,不明确指出来了,考验大家学习这些设计模式的效果,知道的同学可以留言说一下。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 25, 2019 · 2 min · jiezi

观察者模式

概述组成 主题+订阅者(观察者)=观察者模式 主题“接口”(自定义或者Observable类):remove(Object o);add(Object o);notify(); 订阅者”接口“(自定义或者Observer接口):update();定义 观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它所有的依赖者都会收到通知并自动更新。但注意的是,这种一对多的关系并不是绝对的,一个观察者同样可以观察多个主题。设计原则 为了交互对象之间的松耦合而努力。松耦合的设计之所以能让我们建立有弹性的OO系统,能够够应对变化,是因为对象之间的互相依赖降到了最低。 松耦合:两个对象松耦合,它们依然可以交互,但是不清楚彼此的细节。 观察者模式提供了一种对象设计,让主题和观察者之间松耦合:关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类型是哪种、做了些什么或其他任何细节。实现 实现观察者模式的方式不止一种,但其中以包含Subject和Observer接口的类设计最为常见。 注意:在例子中还提供了主题是基类观察者是接口的实例,不推荐的主要原因是继承类只能继承一个这里继承了就无法继承其他的。 观察者模式有主题推送和观察者获取两种类型:订阅/取消功能 无论是对于主题推送类型还是观察者获取类型,都有一个这样的功能的实现:订阅者进行订阅和取消订阅。为什么都是要由订阅者来做,这就是上面的松耦合的设计原则,主题不需要知道订阅者的状态,只需要知道它实现了订阅者接口。为了实现这个功能,首先需要在主题的接口或者基类中中定义相应的public的订阅和取消订阅的方法,比如api中Observable类所实现的: public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } ``public synchronized void deleteObservers() { obs.removeAllElements(); } 上面的这些方法已经能够让订阅者实现自行订阅的不同主题功能(当订阅者只订阅一个主题时,订阅可以放在构造函数中来实现),除此之外,如果想要订阅者能够取消订阅,还需要在订阅者对象中保留相应主题对象的引用(在api中Observer是一个接口),当要取消订阅的时候直接通过主题引用调用deleteObserver(this)方法即可。这样就能够实现订阅者自己来进行订阅主题和取消订阅主题。当一个订阅者订阅多个具体主题实现对象的时候,可以考虑使用容器来进行管理,在取消订阅的时候也可可以遍历容器找到相应的Class类型的主题对象或者找到相同的的主题对象,然后再地调用deleteObserver(this)即可。 api中订阅者和主题两者接口/基类中的成员,如下图所示,在主题基类中可以看到除了我们上面说的:通知、添加订阅者、删除订阅者这些方法之外,还有一系列关于changed成员变量的方法,为什么要引入这个布尔变量呢?原因参考Why do I need to call setChanged before I notify the observers?主题推送类型 主题推送在自定义的的时候就是通过notify()在内部遍历订阅者数组将全部参数统统发给所有订阅者,在这里因为不同订阅者所需的参数不同,所以必须将所有参数都传递过去。订阅者中设置了与要接受的某些参数对应类型的成员变量。(实例可参考) 主题推送在基于api实现的时候就是当参数改变时通过调用notifyObservers(Object arg)将参数放在arg里。在这里因为不同订阅者所需的参数不同,所以必须将所有参数都传递过去。然后订阅者在重写的update(Observable o, Object arg)里去取就行,其他的和自定义基本相同。其中第一个Object o是将主题的引用传递过去了,这是为了让观察者能够知道是哪个主题的通知它的。因为一个观察者可以观察多个主题。观察者获取类型 观察者获取在自定义的时候就是在主题的具体实现里添加上所有数据的get方法(因为数据都是私有的),然后在订阅者在重写的当中update(Object o);其中o是将主题的引用传递过去,通过主题引用来调用get方法获取所想要的参数(不一定是全部了)。当然也可以不这样实现update,直接只是update(),但是需要在订阅者对象中设置有主题对象的引用,同样是根据引用调用get方法获取参数,但这样的做法缺点在于不利于应对一个订阅者订阅多个主题对象的情况。 观察者获取在基于api实现的时候就是当参数改变时通过调用notifyObservers()从而会调用notifyObservers(null),然后调用订阅者重写的update(Observable o, null)方法里来进一步通过主题的引用来调用get方法获取所想要的参数(不一定是全部了)(实例可参考)。其中第一个Object o是将主题的引用传递过去了,这是为了让观察者能够知道是哪个主题的通知它的。因为一个观察者可以观察多个主题。基于API实现的缺点 基于api实现主题有个显著的缺点,因为Observable是一个类,而不是接口,如果继承了Observable类,那就无法继承其他类了。如果必须要继承其他类,那还是需要自行来实现主题接口和主题的具体实现类优点 任何时候我们都可以随时增加观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代旧的观察者,主题不会受到影响。同样,也可以在任何时候删除观察者。 有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所要做的是在新的类里实现此观察者接口,然后注册为观察者即可。 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。 ...

February 23, 2019 · 1 min · jiezi

策略模式

设计原则找出应用中可能需要变化之处,把它们独立出来,不要和哪些那些不需要变化的代码混在一起。面向接口编程,而不是面向实现编程。面向接口编程,真正的意思是面对超类型(supertype)编程,即“变量”的声明类型应该是超类型,通常是一个抽象类或者是一个接口。这样,只要是 具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着声明类时不用理会以后执行的真正的对象类型。多用组合,少用继承。定义 定义了算法族(把具有相同特性的行为看作一族算法,比如鸭子的飞,不同的飞法即不同的算法),分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。 根据相同特性的行为族创建supertype(接口或者抽象类),然后根据supertype构建出具体实现类。在需要该特性的客户基类中加入supertype的实例变量,然后根据子类所需要的行为的不同在子类构造器中为该变量赋予不同的具体实现类对象(根据supertype构建),也可以在基类中加入实例变量的set方法,那么就可以在运行时动态设定行为了(即通过set赋予不同的行为实例)。实现方式定义Strategy和Context接口。Strategy和Context接口必须使得ConcreteStrategy能够有效的访问它所需要的Context中的任何数据,反之亦然。一种办法是让Context将数据放在参数中传递给Strategy操作—也就是说,将数据发送给Strategy。这使得Strategy和Context解耦。但另一方面,Context可能发送一些Strategy不需要的数据。另一种办法是让Context将自身作为一个参数传递给Strategy,该Strategy再显式地向该Context请求数据。或者,Strategy可以存储对它的Context的一个引用,这样根本不再需要传递任何东西。这两种情况下,Strategy都可以请求到它所需要的数据。但现在Context必须对它的数据定义一个更为精细的接口,这将Strategy和Context更紧密地耦合在一起。使Strategy对象成为可选的。如果即使在不使用额外的Strategy对象的情况下,Context也还有意义的话,那么它还可以被简化。Context在访问某Strategy前先检查它是否存在,如果有,那么就使用它;如果没有,那么Context执行缺省的行为。这种方法的好处是客户根本不需要处理Strategy对象,除非它们不喜欢缺省的行为。比如TreeSet类型的对象在创建时可以选择传入Comparator对象也可以传入,这就是策略模式的一个例子。什么时候使用策略模式 解决类直接实现接口造成的方法具体代码不能复用可以考虑采用策略模式,更本质的讲,是想要设计出可复用、可扩展的行为族时。在这里的族可以是根据一个(抽象)基类来创建,也可以像是本例中根据一个接口FlyBehavior来创立。

February 23, 2019 · 1 min · jiezi

装饰者模式

设计原则类应该对扩展开放,对修改关闭。作用 动态地给一个对象添加一些额外的职责。就增加功能而言,装饰者模式相比生成子类更为灵活,因为生成子类,类的行为只能在编译时静态决定。换句话说,行为不是来自父类就是子类覆盖或者添加后的版本。反之,利用装饰者模式可以把装饰者嵌套使用,可以在运行时实现新的装饰者增加新的行为。如果依赖继承,每当我们需要新行为时还需要修改现有的代码,因为使用继承,类的行为只能在编译时静态决定。实现要求接口的一致性。因为装饰者必须必须能取代被装饰者,所以装饰者与被装饰者必须是同一类型,也就是说二者有共同的父类或者实现了共同的接口。 “类型匹配"可以通过抽象类(或者普通基类如IO的实现)、接口来实现。利用组合和委托将被装饰者(组件)添加到装饰者当中。省略不必要时的抽象的Decorator类。当你仅需要添加一个职责时,没有必要定义抽象Decorator类,你尝尝需要处理现存的类层次结构而不是设计一个新系统,这 时你可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。保持接口/父类的简单性。组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予赋予Component太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加装饰者模式与策略模式 改变对象外壳与改变对象内核。我们可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核。例如,Strategy模式就是一个用于改变内核的很好的模式。Component类原本就很庞大时,使用Decorator模式代价太高,Strategy模式相对更好一些。Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。例如我们可以将组件绘制边界的功能延迟到一个独立的Border对象中,这样就可以支持不同的边界风格。这个Border对象是一个Strategy对象,它封装了边界绘制策略。我们可以将策略的数目从一个扩充为任意多个,这样产生的效果与对装饰进行递归嵌套是一样的。装饰者模式的缺点可能会造成大量的小类客户代码中可能依赖某种特定类型(比如某个实现了"接口"的具体类中的非接口方法),如果导入的是装饰者又不做考虑,可能会出现问题。即:Decorator与它的Component不一样,Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识,而是应该依赖接口标识。装饰者模式与代理模式的区别相同点 这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy和decorator对象的实现部分都保留了指向另一个对象的引用,它们向这个对象发送请求。像Decorator模式一样,Proxy模式构成一个对象并为用户提供一致的接口。不同点 但与Decorator模式不同的是,Proxy模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。 在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为Decorator模式中一个必不可少的部分。而在Proxy模式中则不是这样,因为Proxy模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达。模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。结合使用 但这并不意味着这些模式不能结合使用。可以设想有一个proxy-decorator,它可以给proxy添加功能,或是一个decorator-proxy用来修饰一个远程对象。尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。 在我看来Java中的动态代理更像是一个proxy-decorator,也就是代理模式与装饰者模式结合使用。而Java中的静态代理才是我们上面说的那种代理。适用性以下情况使用Decorator模式在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。处理那些可以撤消的职责。当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类,比如final修饰的类或者方法。

February 23, 2019 · 1 min · jiezi

后端知识点整理

知识点面试题https://blog.csdn.net/u011330…知识点https://todayqq.gitbooks.io/p…高级:https://learnku.com/articles/…,https://learnku.com/articles/21048掘金https://juejin.im/post/5adbf3…34.https://juejin.im/post/5c6de7…PHPcookie,sessionMVC:是一种开发模式,M模型负责数据的操作,V视图负责页面的显示,C控制器,负责业务逻辑。OOP:面向对象编程是一种计算机编程架构。三大特性:封装性,继承性,多态性。封装:将一个类的使用和实现分开,只保留部分接口和方法与外部联系。只需要关注类的使用,而不用关心具体实现过程,能有效避免程序依赖,降低耦合。继承:子类自动继承父类的属性和方法,并可以添加新的属性和方法或对部分属性和方法进行重写,增加代码的可重用性。多态:子类继承父类的属性和方法,并对其中部分方法进行重写,增强软件的灵活性。设计模式单例模式:保证系统中类仅有一个对象实例。1.构造函数私有。2.定义该类的静态私有对象。3.定义一个静态的公共函数。4.禁止拷贝对象(重写__clone(), 空实现)应用场景:线程池,缓存,日志对象,数据库操作工厂模式:最常用的实例化对象模式,用工厂方法代替new操作。优点:如果想要更改实例化类名只需要修改该工厂方法,为系统结构提供灵活的动态扩展机制,减少耦合。观察者模式:当一个对象状态发生变化时,依赖它的对象会全部收到通知并自动更新。优点:实现了低耦合非侵入式的通知与更新机制。适配器模式:将各种不同的函数接口封装成统一的API 。框架中的DB类就是采取这种模式策略模式:是对象的行为模式,对一组算法进行封装,动态的选择需要的算法并使用。单点登录:一个多系统共存的环境下,用户在移除登录后,就不用在其他系统中登录。MySQLQ:常用SQL语句Q:数据库优化主从复制,读写分离选择合适的存储引擎,采用三范式(表中所有数据不但能唯一被关键字标识,还必须相互独立)使用分区分表,字段选用合适类型,适当冗余,开启MySQL缓存SQL优化,索引优化,复制,分区:数据分区是一种物理数据库设计技术,目的是为了在特定的SQL操作中减少数据库总量以缩减响应时间。分区不是生成新表,而是将表的数据均衡分摊到不同的硬盘、系统或是不同服务器存储介质中分片:乐观锁,悲观锁,分布式锁悲观锁:乐观锁:分布式锁:Q:处理负载,高并发1.NoSQLQ:Redis消息队列先进先出的注意事项网络数据结构及算法

February 23, 2019 · 1 min · jiezi

行为型模式:命令模式

LieBrother原文:行为型模式:命令模式十一大行为型模式之三:命令模式。简介姓名 :命令模式英文名 :Command Pattern价值观 :军令如山个人介绍 :Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。(来自《设计模式之禅》)你要的故事作为一个程序猿,我们每天都在经历着命令模式,技术经理把需求任务分配给工程师开发,有时因为第三方或者其他不可抗拒的因素导致需求停止开发。这种工作模式就是命令模式。好了,开始故事了。小明在 XX 科技公司做一个安静的程序猿,有一天技术经理给他分配了一个任务:新增黑名单,也就是在他们系统的某个模块里面可以手工对电话打黑名单标签的功能。小明接到任务后就立马开发,在开发了 2 天之后,因为战略原因,技术经理大明暂停了这个开发任务,接下来我们通过非命令模式和命令模式 2 种代码实现来体现这个过程。在这个场景中,为了简单,我们假定技术经理大明手下只有小明一个开发人员。非命令模式非命令模式也就是不使用命令模式的代码实现。代码中,我们出现了 Developer 开发人,开发同学是接受技术经理传达的任务,技术经理让他开发哪个需求就开发哪个需求,如果项目有问题需要中断,也需要技术经理评估后传达给开发同学,所以 Developer 有 2 个方法,分别是 develop() 开发需求和 suspend() 暂停需求。 Requirement 则为需求类,TechnicalManager1 则为技术经理类,他有一个方法 action(),通过这个方法来指定开发同学开发任务或者暂停任务。public class NoCommandTest { public static void main(String[] args) { Developer xiaoMing = new Developer(“小明”); Requirement requirement = new Requirement(“新增黑名单”); TechnicalManager1 technicalManager2 = new TechnicalManager1(“大明”); technicalManager2.setDeveloper(xiaoMing); technicalManager2.action(requirement, “develop”); System.out.println(“开发了 2 天,需求变故,需要暂停。。。”); technicalManager2.action(requirement, “suspend”); }}/** * 开发人员 /class Developer { private String name; public Developer(String name) { this.name = name; } public void develop(Requirement requirement) { System.out.println(this.name + " 开始开发需求:" + requirement.getName()); } public void suspend(Requirement requirement) { System.out.println(this.name + " 停止开发需求:" + requirement.getName()); } public String getName() { return name; }}/* * 需求 /class Requirement { private String name; public Requirement(String name) { this.name = name; } public String getName() { return name; }}/* * 技术经理 /class TechnicalManager1 { private String name; private Developer developer; public TechnicalManager1(String name) { this.name = name; } public void setDeveloper(Developer developer) { this.developer = developer; } public void action(Requirement requirement, String type) { if (“develop”.equals(type)) { this.developer.develop(requirement); } else if (“suspend”.equals(type)) { this.developer.suspend(requirement); } }}打印结果:小明 开始开发需求:新增黑名单开发了 2 天,需求变故,需要暂停。。。小明 停止开发需求:新增黑名单通过代码,我们可以发现技术经理和开发同学是强依赖关系。如果技术经理下达了一个任务,要求小明写一下周报,这时候得怎么写?是不是小明需要一个写周报的方法,大明也需要新增一个处理事务类型?有没有更好的方法让技术经理不需要做任何改变?命令模式就来解决这个问题。命令模式在这个例子中,不管大明叫小明做什么事情,其实都是一样的,就是下达任务命令,让小明去执行命令。我们可以利用命令模式把下达任务这个抽象起来,当做父类,下达开发命令、下达暂停命令、下达写周报等等都是不同的子命令。代码如下。public class CommandTest { public static void main(String[] args) { Developer xiaoMing = new Developer(“小明”); Command developCommand = new DevelopCommand(xiaoMing); Command suspendCommand = new SuspendCommand(xiaoMing); Requirement requirement = new Requirement(“新增黑名单”); TechnicalManager2 technicalManager = new TechnicalManager2(“大明”); technicalManager.setCommand(developCommand); technicalManager.action(requirement); System.out.println(“开发了 2 天,需求变故,需要暂停。。。”); technicalManager.setCommand(suspendCommand); technicalManager.action(requirement); }}/* * 命令 /abstract class Command { protected Developer developer; public Command(Developer developer) { this.developer = developer; } public abstract void execute(Requirement requirement);}/* * 开始开发 /class DevelopCommand extends Command { public DevelopCommand(Developer developer) { super(developer); } @Override public void execute(Requirement requirement) { this.developer.develop(requirement); }}/* * 开发中断 /class SuspendCommand extends Command { public SuspendCommand(Developer developer) { super(developer); } @Override public void execute(Requirement requirement) { this.developer.suspend(requirement); }}/* * 技术经理 */class TechnicalManager2 { private String name; private Command command; public TechnicalManager2(String name) { this.name = name; } public void action(Requirement requirement) { this.command.execute(requirement); } public void setCommand(Command command) { this.command = command; }}打印结果:小明 开始开发需求:新增黑名单开发了 2 天,需求变故,需要暂停。。。小明 停止开发需求:新增黑名单代码中用 Command 来抽象下达任务,而技术经理 TechnicalManager2 并没有和 Developer 有直接的关系,而是 TechnicalManager2 和 Command 建立的联系,Command 和 Developer 建立了联系。这样子把大明和小明的强依赖关系给剥离开,而新增一个下达写周报的任务也很简单,在 Developer 中新增一个处理写周报的方法,新增一个写周报的 Command 子类,就可以了,TechnicalManager2 如上面所愿不用修改。这就是完整的一个命令模式代码。代码:Command Pattern总结从文章中我们就可以看到,利用命令模式能够进行类的解耦,让调用者和接受者没有任何关系,也通过对行为的抽象,让新增其他行为变得清晰容易,也就是可扩展性大大增加。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 22, 2019 · 2 min · jiezi

行为型模式:中介者模式

LieBrother公众号原文:行为型模式:中介者模式十一大行为型模式之二:中介者模式。简介姓名 :中介者模式英文名 :Mediator Pattern价值观 :让你体验中介是无所不能的存在个人介绍 :Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。(来自《设计模式之禅》)你要的故事看了这小伙子的名字,大家会很直观的想到那些拿了我们半个月租的租房中介同学。在这不讲讲房租中介同学,以后可没机会了。大家现在找房子,不管是买还是租,一登录什么安居客、58同城,是不是有 80% 是经纪人房源,说 80% 还是比较保守的,经历了 4 次找房,发现个人房源越来越少。每个网站都有个选项:经纪人房源。如下图:(图片截自:安居客网站)经纪人就扮演着中介的角色,和本文要讲的中介者模式完全吻合。我们在找房子的时候,经纪人扮演什么角色呢?我们通过个人房源和经纪人房源的租房案例来简单描述经纪人的角色。个人房源我们通过个人房源找房子的方式是这样的:在网上找个人房源的房东,然后挨个联系,和房东约定好时间去看房,我们跟房东的关系是一对多的关系。小明就在网上看了个人房源,联系了房东,分别去看了农民房和小区房,用代码表示如下。public class PersonalTest { public static void main(String[] args) { Tenant xiaoMing = new Tenant(“小明”); xiaoMing.lookAtHouse(); }}class Tenant { private String name; private XiaoQuFangLandlord xiaoQuFangLandlord2 = new XiaoQuFangLandlord(); private NongMinFangLandlord nongMinFangLandlord2 = new NongMinFangLandlord(); public Tenant(String name) { this.name = name; } public void lookAtHouse() { System.out.println(this.name +“想看农民房”); nongMinFangLandlord2.supply(); System.out.println(this.name + “想看小区房”); xiaoQuFangLandlord2.supply(); }}/** * 房东 /abstract class Landlord { // 提供房子 public abstract void supply();}class XiaoQuFangLandlord extends Landlord { @Override public void supply() { System.out.println(“小区房的房东提供一间小区房”); }}class NongMinFangLandlord extends Landlord { @Override public void supply() { System.out.println(“农民房的房东提供一间小区房”); }}打印结果如下:小明想看农民房农民房的房东提供一间小区房小明想看小区房小区房的房东提供一间小区房小明分别联系小区房的房东和农民房的房东,然后依次去看了农民房和小区房。这样子有个弊端就是小明和房东是强关联的关系,其实小明只是去看一下房,看完不想租就和房东没啥关系了。这个时候经纪人就派上用场了,经纪人的主要任务就是把房子租出去,所以他和房东应该是强关系,直到把房子成功租出去了,才和房东脱离关系,而小明也不用去挨个找房东看房子了,这个职责转给经纪人,小明只需要联系一个人,那就是经纪人,跟他说我要看小区房和农民房,经纪人就带他去看。下面就介绍经纪人房源的方式,也就是本文要讲的中介者模式。经纪人房源用经纪人房源找房子,小明就省心很多了,小明就只联系了一个经纪人,跟他描述了自己要的房源:小区房和农民房都可以,经纪人里面和他约定了一个下午的时间,把小明所有想看的房让他看完,最终小明决定租了一间房。看代码。public class MediatorTest { public static void main(String[] args) { System.out.println(“小明想要看小区房和农民房”); Tenant2 xiaoMing = new Tenant2(“小明”, Arrays.asList(“XiaoQuFang”, “NongMinFang”)); xiaoMing.lookAtHouse(); }}/* * 租客 /class Tenant2 { private String name; private List<String> wantTypes; private RentingMediator rentingMediator = new RentingMediator(); public Tenant2(String name, List<String> wantTypes) { this.name = name; this.wantTypes = wantTypes; } public void lookAtHouse() { rentingMediator.supplyHouse(wantTypes); }}/* * 中介抽象类 /abstract class Mediator { // 看房 public abstract void supplyHouse(List<String> types);}/* * 租房中介 */class RentingMediator extends Mediator { private XiaoQuFangLandlord xiaoQuFangLandlord; private NongMinFangLandlord nongMinFangLandlord; public RentingMediator() { xiaoQuFangLandlord = new XiaoQuFangLandlord(); nongMinFangLandlord = new NongMinFangLandlord(); } @Override public void supplyHouse(List<String> types) { System.out.println(“经纪人提供了如下房源”); if (types.contains(“XiaoQuFang”)) { xiaoQuFangLandlord.supply(); } if (types.contains(“NongMinFang”)) { nongMinFangLandlord.supply(); } }}打印结果:小明想要看小区房和农民房经纪人提供了如下房源小区房的房东提供一间小区房农民房的房东提供一间小区房在代码中,我们可以看到小明和经纪人是一对一关系,经纪人和房东是一对多关系。小明找房经历也轻松多了,只花了一下午就把房子都看了并看中了。这也是中介者模式的优点,减少了不必要的依赖,降低了类间的耦合。代码:Mediator Pattern总结中介者模式通过在互相依赖的对象中间加了一层,让原本强依赖的对象变成弱依赖。在软件编程中,有一个中介者模式的典型的例子,就是 MVC 框架,也称三层架构,通过 Controller (控制层) 将 Model (业务逻辑层) 和 View (视图层) 的依赖给分离开,协调 Model 和 View 中的数据和界面交互工作。看看你工作中的代码,想想看有没有哪些对象之间的关系特紧密特混乱,考虑是不是可以通过中介者模式来把依赖关系剥离,让代码更清晰。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:行为型模式:模板方法公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 18, 2019 · 2 min · jiezi

行为型模式:模板方法

LieBrother公众号原文:行为型模式:模板方法十一大行为型模式之一:模板方法。简介姓名 :模板方法英文名 :Template Method Pattern价值观 :在我的掌控下,任由你发挥个人介绍 :Define the skeleton of an algorithm in an operation,deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(来自《设计模式之禅》)解释一下上面的介绍,意思是由父类来定义框架,让子类来具体实现。你要的故事刚过完春节,大家都买新鞋了么?今天要讲的故事和鞋子有关。一双鞋子从表面来看,由鞋底、鞋垫、鞋面、鞋带组成,同一系列的鞋子这几个部分都是一样的,用同样的材料做出来,不同系列的鞋子就大相径庭了。根据模板方法模式,组装一双鞋子的制造过程可以归并为固定的框架,至于用什么材料,那由每个系列的鞋子去具体实现。我们先看定义组装鞋子的框架代码。/** * 定义鞋子制造的工序框架 /abstract class ShoeInstallTemplate { public abstract void installSole(); public abstract void installInsole(); public abstract void installVamp(); public abstract void installShoelace(); public void installShot(){ System.out.println(“组装一双鞋,步骤如下:”); // 组装鞋底 installSole(); // 组装鞋垫 installInsole(); // 组装鞋面 installVamp(); // 组装鞋带 installShoelace(); }}定义了一个组装鞋子框架的抽象类 ShoeInstallTemplate,里面有 4 个工序未具体实现,由鞋子制造商去实现,因为只有鞋子制造商才知道鞋子要用什么材料来做。下面举 2 个比较出名的鞋子:Adidas 的 Boost 系列和 Nike 的 Jordan 系列。下面分别实现这 2 个系列鞋子的制造代码。/* * Adidas Boost 鞋制造 /class AdidasBoostShoeInstall extends ShoeInstallTemplate { @Override public void installSole() { System.out.println(“组装白色 Boost 鞋底”); } @Override public void installInsole() { System.out.println(“组装黑色 Boost 鞋垫”); } @Override public void installVamp() { System.out.println(“组装黑色 Boost 鞋面”); } @Override public void installShoelace() { System.out.println(“组装黑色 Boost 鞋带”); }}/* * Nike Jordan 鞋制造 */class NikeJordanShoeInstall extends ShoeInstallTemplate { @Override public void installSole() { System.out.println(“组装黑色 Jordan 鞋底”); } @Override public void installInsole() { System.out.println(“组装黑色 Jordan 鞋垫”); } @Override public void installVamp() { System.out.println(“组装红色 Jordan 鞋面”); } @Override public void installShoelace() { System.out.println(“组装红色 Jordan 鞋带”); }}实现了制造商制造鞋子的代码之后,我们通过代码测试怎么制造 Boost 和 Jordan 鞋子。public class TemplateMethodTest { public static void main(String[] args) { ShoeInstallTemplate adidasBoost = new AdidasBoostShoeInstall(); adidasBoost.installShot(); ShoeInstallTemplate nikeJordan = new NikeJordanShoeInstall(); nikeJordan.installShot(); }}打印结果:组装一双鞋,步骤如下:组装白色 Boost 鞋底组装黑色 Boost 鞋垫组装黑色 Boost 鞋面组装黑色 Boost 鞋带组装一双鞋,步骤如下:组装黑色 Jordan 鞋底组装黑色 Jordan 鞋垫组装红色 Jordan 鞋面组装红色 Jordan 鞋带模板方法模式就这么简单。是不是掌握了?代码:Template Method Pattern总结模板方法是一个比较实用的模式,为什么说实用呢?举个现实的例子,Java 能有如今的发展,离不开各大开源框架,比如 Dubbo,有看过源码的朋友就知道,里面大量代码运用了模板方法设计模式,为什么 Dubbo 可以支持很多种注册中心?其实本质就是用了模板方法设计模式,使得可以扩展多种注册中心。掌握好模板方法,对读源码有非常大的帮助,很多人包括我在内,在刚开始阅读源码的时候,有相当长的一段时间怀疑人生,怎么这些代码那么绕?调来调去的。当你了解了常用的设计模式之后,看源代码就可以直截了当的知道是用什么设计模式,为什么用这个设计模式?原来是为了什么什么。。。有了这层思考,就像有一条线将以前散落在各地的知识点连接起来,成了可以推敲的知识。参考资料:《大话设计模式》、《设计模式之禅》推荐阅读:设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

February 17, 2019 · 2 min · jiezi

实用的设计模式1——单例模式

在软件工程中,设计模式( design pattern )是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。看维基上对设计模式的定义,你就知道设计模式的重要性,但是往往编程中设计模式要不就是感觉高深莫测要不就是热衷于概念,所以写这个系列的文章,主要谈谈怎么把设计模式真正的用到平时的编程中,希望对大家有帮助。单例模式下面是一个创建登录框的例子:var createLoginLayer = function(){ var div = document.createElement( ‘div’ ); div.innerHTML = ‘我是登录浮窗’; div.style.display = ’none’; document.body.appendChild( div ); return div;};document.getElementById( ’loginBtn’ ).onclick = function(){ var loginLayer = createLoginLayer(); loginLayer.style.display = ‘block’;};这里有个问题就是如果这个登录框已经存在的情况下,就不应该再创建了,修改下代码:var loginLayer = null;var createLoginLayer = function(){ var div = document.createElement( ‘div’ ); div.innerHTML = ‘我是登录浮窗’; div.style.display = ’none’; document.body.appendChild( div ); return div;};document.getElementById( ’loginBtn’ ).onclick = function(){ loginLayer = loginLayer || createLoginLayer(); loginLayer.style.display = ‘block’;};我们用一个全局变量储存了生成的layer,如果不存在再去创建它。我们把单例的条件改复杂点,比如我们这段程序需要在多个页面用到,但是每个页面都有自己的登录框,现在修改下程序var loginLayer = null;var createLoginLayer = function(title){ var div = document.createElement( ‘div’ ); div.innerHTML = title; div.style.display = ’none’; document.body.appendChild( div ); return div;};var loginHandle = function(title) { loginLayer = loginLayer || createLoginLayer(title); loginLayer.style.display = ‘block’;}var addLoginHandle = function(id, title) { document.getElementById( id ).onclick = function(){ loginHandle(title) };}// 主页的登陆按钮addLoginHandle(‘mainLoginBtn’, ‘主页’);// 这是帮助页的登陆按钮addLoginHandle(‘helpLoginBtn’, ‘帮助中心’’);你发现了吗,如果在主页中先点击登陆按钮,然后在帮助页面点登陆按钮就不会重新创建登录页了,我们再改下程序var loginLayer = null;var createLoginLayer = function(title){ var div = document.createElement( ‘div’ ); div.innerHTML = title; div.style.display = ’none’; document.body.appendChild( div ); return div;};var loginHandle = function(title) { if(!loginLayer || loginLayer.innerHTML !== title) { loginLayer = createLoginLayer(title); } loginLayer.style.display = ‘block’;}var addLoginHandle = function(id, title) { document.getElementById( id ).onclick = function(){ loginHandle(title) };}// 主页的登陆按钮addLoginHandle(‘mainLoginBtn’, ‘主页’);// 这是帮助页的登陆按钮addLoginHandle(‘helpLoginBtn’, ‘帮助中心’’);到此一个普通的单例其实就已经结束了,但这并不是本文最重点的地方,再看一看设计模式的定义:软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。我们的主要目的是把程序中公用的能普遍适用的地方提取出来:var getSingle = function( fn, getNewSingle ){ var result; return function(){ return ((getNewSingle && getNewSingle(result)) || (!getNewSingle && result) || ( result = fn.apply(this, arguments); } };var createLoginLayer = function(title){ var div = document.createElement( ‘div’ ); div.innerHTML = title; div.style.display = ’none’; document.body.appendChild( div ); return div;};var loginHandle = function(title) { var loginLayer = getSingle(createLoginLayer, function(layer){ return layer.innerHTML === title; }); loginLayer.style.display = ‘block’;}var addLoginHandle = function(id, title) { document.getElementById( id ).onclick = function(){ loginHandle(title) };}// 主页的登陆按钮addLoginHandle(‘mainLoginBtn’, ‘主页’);// 这是帮助页的登陆按钮addLoginHandle(‘helpLoginBtn’, ‘帮助中心’’);你可以看到 getSingle 这个函数就是我们用来实现单例模式的函数,一方面提取了一个公用的函数,让思路更清晰;另一方面用闭包的形式减少了一个全局变量。总结: 写到最后,其实单例模式在js中就是一个高阶函数,把这个函数记住然后会用,单例就基本掌握了。// 如果没传getNewSingle,就生成普通的单例就行了,否则根据条件生成(根据属性生成多个单例的情况)var getSingle = function( fn, getNewSingle ){ var result; return function(){ return ((getNewSingle && getNewSingle(result)) || (!getNewSingle && result) || ( result = fn.apply(this, arguments); } }; ...

February 15, 2019 · 2 min · jiezi

Python版设计模式之单例模式

单例模式在某些场景下,我们希望实体类无论实例化多少次都只会产生一个实体对象,这时候就需要使用单例模式。经常使用的场景就是全局配置类。模式框架方式1:使用修饰器"““使用函数定义装饰器”““def singletons(cls): "”” 定义一个单例装饰器,使用dict保存定义好的实体,key为class的地址而不是名字,这样同名类也不会冲突 "”" instances = {} def wrapper(*args, **kwargs): if cls not in instances.keys(): instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper"““使用类定义装饰器”““class singletons(object): instances = {} def init(self, cls): self.__cls = cls def call(self, *args, **kwargs): if self.__cls not in singletons.instances.keys(): singletons.instances[self.__cls] = self.__cls(*args, **kwargs) return singletons.instances[self.__cls]方式2:重写__new__方法,只能针对当前修改的类有效class SingletonTest(object): __instance = None __isFirstInit = False def new(cls, name): if not cls.__instance: SingletonTest.__instance = super().new(cls) return cls.__instance def init(self, name): if not self.__isFirstInit: self.__name = name SingletonTest.__isFirstInit = True def getName(self): return self.__nameUML图示例@singletonsclass Test(object): def init(self, name): self.__name = name def hello(self): print(“I am {} object {}".format(self.__name, id(self)))if name == “main”: test1 = Test(“test1”) test2 = Test(“test2”) test1.hello() test2.hello()‘‘‘测试输出’’’# I am test1 object 2453169112512# I am test1 object 2453169112512扩展思考单例模式的线程安全问题。 ...

February 15, 2019 · 1 min · jiezi

Python版设计模式之监听者模式

监听模式又名观察者模式、发布/订阅模式、源-监听器(Source/Listener)模式,模式的核心是:设计时要区分谁是被观察者,谁是观察者。被观察者至少有三个方法,添加观察者、删除观察者、监听目标变化并通知观察者;观察者这至少包含一个方法,当接收到被观察者的通知时,做出相应的处理(即在被观察者的监听中调用)。模式框架’‘‘观察者模式’‘‘class Observable(object): ’’’ 被监听的对象,实现类需要具体增加被监听的资源 ’’’ def init(self): self.__observers = [] @property def observers(self): return self.__observers def has_observer(self): return False if not self.__observers else True def add_observer(self, observer): self.__observers.append(observer) def remove_observer(self, observer): self.__observers.remove(observer) def listener(self, obj=None): for observer in self.__observers: observer.update(self, obj)class Observer(object): ’’’ 观察者,当观察的对象发生变化时,依据变化情况增加处理逻辑 ’’’ def update(self, observable, obj): passUML图示例’‘‘基于观察者模式,实现一个简单的消息队列,当队列中有消息时,将消息发送给监听者’‘‘class MyQueue(Observable): def init(self): super().init() self.__resource = [] def has_message(self): return True if self.__resource else False def queue_size(self): return len(self.__resource) def add_resource(self, res): self.__resource.append(res) print(“新消息进入,推送…”) self.listener(obj=res)class MySubOdd(Observer): def update(self, observable, obj): if isinstance(observable, MyQueue) and int(obj) % 2 != 0: print(“I’m MySubOdd, Get Message {} from MyQueue.".format(obj))class MySubEven(Observer): def update(self, observable, obj): if isinstance(observable, MyQueue) and int(obj) % 2 == 0: print(“I’m MySubEven, Get Message {} from MyQueue.".format(obj))if name == “main”: my_queue = MyQueue() # 初始化一个队列 my_sub_odd = MySubOdd() # 初始化奇数监听者 my_sub_even = MySubEven() # 初始化偶数监听者 # 将两个监听者加入监听队列 my_queue.add_observer(my_sub_odd) my_queue.add_observer(my_sub_even) # 添加资源进队列 my_queue.add_resource(“1”) my_queue.add_resource(“3”) my_queue.add_resource(“2”) my_queue.add_resource(“4”) ...

February 15, 2019 · 1 min · jiezi

前端设计模式

1、模块模式在立即执行函数表达式中定义的变量和方法,在该函数外部是访问不到的,只能通过该函数提供的接口,“有限制的"进行访问;通过函数的作用域,解决了属性和方法的封装问题。最常见的立即执行函数写法有以下两种:(function(){ /* code / }())或者(function(){ / code */ })()模块模式代码: let Person = (function(){ var age = “12”; var name = “jerry”; function getAge(){ return age; } function getName(){ return name; } return { getAge: getAge, getName: getName } })() console.log(age, ‘age’); // 报错: Uncaught ReferenceError: age is not defined console.log(name, ’name’); // 空字符串,为啥不报错?看底部备注 console.log(Person.age); // undefined console.log(Person.name); // undefined // 只能通过Person提供的接口访问相应的变量 console.log(Person.getName()); // jerry console.log(Person.getAge()); // 122、构造函数模式 function Person(name,age){ this.name = name; this.age = age; } Person.prototype.printName = function(){ console.log(this.name) } Person.prototype.printAge = function(){ console.log(this.age) } function Student(name,age){ // 继承 Person 的属性 Person.call(this,name,age) } function create(prototype){ function F(){} F.prototype = prototype return new F() } // 让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法 Student.prototype = create(Person.prototype) Student.prototype.printAge = function(){ console.log(this.age) } let student = new Student(‘jerry’,12) student.printName() // “jerry” student.printAge() // “12"3、混合模式 function Person(name,age){ this.name = name this.age = age } Person.prototype.printName = function(){ console.log(this.name) } function Student(name,age){ // 继承 Person 的属性 Person.call(this, name, age) } function create(prototype){ function F(){} F.prototype = prototype return new F() } // 让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法 Student.prototype = create(Person.prototype) Student.prototype.printAge = function(){ console.log(this.age) } let student = new Student(‘jerry’, 12) student.printName() // “jerry” student.printAge() // 124、工厂模式 function Person(name, age){ let person = new Object() person.name = name person.age = age person.printName = function(){ console.log(this.name) } person.printAge = function(){ console.log(this.age) } return person } let person = Person(‘jerry’,12) person.printName() person.printAge()5、单例模式 let Singleton = (function(){ let instantiated function init(){ /定义单例代码/ return{ publicMethod: function(){ console.log(“Hello World”); }, publicProperty: “Test” } } return{ getInstance: function(){ if(!instantiated){ instantiated = init() } return instantiated } } }()) Singleton.getInstance().publicMethod()单例之间的通讯:建立两个独立的对象:jim&&lily,两者之间通过door直接通讯,如果没有新建door,有直接通讯。代码如下: let jim = (function(argument){ let door let jimHome = function(msg){ this.doorbell = msg } let info = { sendMessage: function(msg){ if(!door){ door = new jimHome(msg) } return door }, coming: function(){ return “來了來了” } } return info }()) let lily = { callJim: function(msg){ let _xw = jim.sendMessage(msg) alert(_xw.doorbell) console.log(_xw.doorbell) _xw = null // 等待垃圾回收 let coming = jim.coming() console.log(coming) } } lily.callJim(“叮咙”)6、发布-订阅模式订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身主题变化时,会通知所有订阅者对象,使他们能够自动更新自己的状态。将一个系统分割成一系列相互协作的类有一个很不好的副作用:需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且他不知道具体有多少对象需要改变时,此时建议使用订阅发布模式。应用场景:DOM事件。DOM事件是一种典型的发布-订阅模式,对一个DOM节点的DOM事件进行监听;当操作DOM节点时,触发相应的事件并执行函数。自定义时间。指定发布者,类似于一个对象(key:value);key表示事件的名称,value是一个数组;发布消息后,遍历value的数组,依次执行订阅者的回调函数。应用Demo如下: let Event = (function(){ var events = {} function on(evt, handler){ events[evt] = events[evt]||[]; events[evt].push({ handler:handler }) } function fire(evt,args){ if(!events[evt]){ return } for(var i=0;i<events[evt].length;i++){ events[evt][i].handler(args) } } function off(evt){ delete events[evt] } return { on: on, fire: fire, off: off } }()) Event.on(‘change’, function(val){ console.log(‘change事件,value is’ + val) }) Event.on(‘click’, function(val){ console.log(‘click事件,value is ‘+ val) }) Event.fire(‘change’, ‘jerry1’) Event.fire(‘click’, ‘jerry2’) Event.off(‘change’)备注:console.log(name, ’name’)没有报错,原因在于name是浏览器的窗口变量名,已存在于浏览器内部。 ...

February 12, 2019 · 2 min · jiezi

????一起切换到 Node.js 平台

嘿,这里有一本 Node.js 完全指南。此书来自 Packet 出版社的 Node.js Design Patterns - Second EditionGet the best out of Node.js by mastering its most powerful components and patterns to create modular and scalable applications with ease此书解析了 Node.js 的前世今生,以 Node.js 设计哲学为核心,循循善诱地带你走进 Node.js 的世界。全书串联了以下几大主题:Node.js 基础模式、异步代码控制流、流模型、模块的连接方式、适用于 Node.js 的设计模式、通用的 JavaScript 应用及一些高级技巧。虽是俺翻译的但与俺无任何利益关系。因涉及版权问题,此书只供学习交流,禁止任何转载。书籍最后两章的高级技巧篇还在翻译中,有兴趣的小伙伴可以一起加入哦。非常欢迎大家提 Pr、Issues。????一起切换到 Node.js 平台

February 6, 2019 · 1 min · jiezi

利用策略模式优化过多 if else 代码

前言不出意外,这应该是年前最后一次分享,本次来一点实际开发中会用到的小技巧。比如平时大家是否都会写类似这样的代码:if(a){ //dosomething}else if(b){ //doshomething}else if(c){ //doshomething} else{ ////doshomething}条件少还好,一旦 else if 过多这里的逻辑将会比较混乱,并很容易出错。比如这样:摘自 cim 中的一个客户端命令的判断条件。刚开始条件较少,也就没管那么多直接写的;现在功能多了导致每次新增一个 else 条件我都得仔细核对,生怕影响之前的逻辑。这次终于忍无可忍就把他重构了,重构之后这里的结构如下:最后直接变为两行代码,简洁了许多。而之前所有的实现逻辑都单独抽取到其他实现类中。这样每当我需要新增一个 else 逻辑,只需要新增一个类实现同一个接口便可完成。每个处理逻辑都互相独立互不干扰。实现按照目前的实现画了一个草图。整体思路如下:定义一个 InnerCommand 接口,其中有一个 process 函数交给具体的业务实现。根据自己的业务,会有多个类实现 InnerCommand 接口;这些实现类都会注册到 Spring Bean 容器中供之后使用。通过客户端输入命令,从 Spring Bean 容器中获取一个 InnerCommand 实例。执行最终的 process 函数。主要想实现的目的就是不在有多个判断条件,只需要根据当前客户端的状态动态的获取 InnerCommand 实例。从源码上来看最主要的就是 InnerCommandContext 类,他会根据当前客户端命令动态获取 InnerCommand 实例。第一步是获取所有的 InnerCommand 实例列表。根据客户端输入的命令从第一步的实例列表中获取类类型。根据类类型从 Spring 容器中获取具体实例对象。因此首先第一步需要维护各个命令所对应的类类型。所以在之前的枚举中就维护了命令和类类型的关系,只需要知道命令就能知道他的类类型。这样才能满足只需要两行代码就能替换以前复杂的 if else,同时也能灵活扩展。InnerCommand instance = innerCommandContext.getInstance(msg);instance.process(msg) ;总结当然还可以做的更灵活一些,比如都不需要显式的维护命令和类类型的对应关系。只需要在应用启动时扫描所有实现了 InnerCommand 接口的类即可,在 cicada 中有类似实现,感兴趣的可以自行查看。这样一些小技巧希望对你有所帮助。以上所有源码可以在这里查看:https://github.com/crossoverJie/cim你的点赞与分享是对我最大的支持

January 30, 2019 · 1 min · jiezi

JavaScript装饰者模式

本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于设计模式的看法,推荐看看本书作者的建议。什么是装饰者模式?给对象动态增加职责的方式成为装饰者模式。装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活的做法,装饰者是一种“即付即用”的方式,比如天冷了就多穿一件外套。装饰函数想要为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,但是这是最差的办法,直接违反了开放——封闭原则。var a = function(){ alert(1)}// 改成var a = function(){ alert(1) alert(2)}很多时候我们不想碰原函数,也许原函数是其他同事编写的,甚至在一个古老的项目中,这个函数的源代码被隐藏在一个我们不愿触碰的阴暗角落里。现在需要不改变源代码的情况下,给函数增加功能。我们通过保存原引用的方式改写某个函数。var a = function(){ alert(1)}var _a = aa = function(){ _a() alert(2)}a()这是实际开发中很常见的一个做法,比如我们想给 window 绑定 onload 事件,但是又不确定这个事件是不是已经被其他人绑定过,为了避免覆盖掉之前的 window.onload 函数中的行为,先保存 window.onload,把它放入新的 window.onload。window.onload = function(){ alert(1)}var _onload = window.onload || function(){}window.onload = funtion(){ _onload() alert(2)}这样的代码是符合封闭——开放原则,我们在增加新功能的时候确实没有修改原来的代码,但是这种方式存在两个问题:必须维护 _onload 这个中间变量,虽然看起来不起眼,但是如果函数装饰链较长,或者需要装饰的函数变多,这些中间变量的数量也会越来越多。其实还遇到了 this 被劫持的问题,在 window.onload 的例子中没有这个烦恼,因为调用 _onload 的时候 this 也指向 window,跟调用 window.onload 的时候一样。用 AOP 装饰函数AOP(Aspect Oriented Programming)面向切面编程的主要作用是:把一些跟核心业务逻辑无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来以后,再通过“动态织入”的方式掺入业务逻辑模块中。这样的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。首先给出 Function.prototype.before 和 Function.prototype.after 方法:Function.prototype.before = function(beforefn){ // 保存原函数的引用 var _self = this // 返回包含原函数和新函数的“代理函数” return function(){ // 执行新函数,保证this不被劫持, // 新函数接受的参数也会被原封不动地传入原函数, // 新函数在原函数之前执行 beforefn.apply(this,arguments) return _self.apply(this.arguments) }}Function.prototype.after = function(afterfn){ var _self = this return function(){ var ret = _self.apply(this,arguments) afterfn.apply(this,arguments) return ret }}“代理函数”只是结构上像代理而已,并不承担代理的职责(比如控制对象的访问),它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序。再回到 window.onload 的例子中,用 Function.prototype.after 来增加新事件:window.onload = function(){ alert(1)}window.onload = (window.onload || function(){}).after(function(){ alert(2)}).after(function(){ alert(3)})AOP 的应用实例(1)数据统计上报<html> <button tag=“login” id=“button”>点击打开登录浮层</button> <script> var showLogin = function(){ console.log( ‘打开登录浮层’ ) log( this.getAttribute( ’tag’ ) ) } var log = function( tag ){ console.log( ‘上报标签为: ’ + tag ) } document.getElementById( ‘button’ ).onclick = showLogin </script></html>showLogin 函数既要负责打开浮层,又要负责数据上报,两个功能耦合在一个函数里,使用 AOP 分离:<html><button tag=“login” id=“button”>点击打开登录浮层</button><script> Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ) afterfn.apply( this, arguments ) return ret } } var showLogin = function(){ console.log( ‘打开登录浮层’ ) } var log = function(){ console.log( ‘上报标签为: ’ + this.getAttribute( ’tag’ ) ) } showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据 document.getElementById( ‘button’ ).onclick = showLogin;</script></html>(2) 插件式表单验证<html><body> 用户名:<input id=“username” type=“text”/> 密码: <input id=“password” type=“password”/> <input id=“submitBtn” type=“button” value=“提交”></button></body><script> var username = document.getElementById( ‘username’ ), password = document.getElementById( ‘password’ ), submitBtn = document.getElementById( ‘submitBtn’ ); var formSubmit = function(){ if ( username.value === ’’ ){ return alert ( ‘用户名不能为空’ ); } if ( password.value === ’’ ){ return alert ( ‘密码不能为空’ ); } var param = { username: username.value, password: password.value } ajax( ‘http:// xxx.com/login’, param ); // ajax 具体实现略 } submitBtn.onclick = function(){ formSubmit(); }</script></html>formatSubmit 函数承担了两个职责,除了提交ajax请求,还要验证用户输入的合法性。我们把校验输入的逻辑放到validata函数中,并约定当validata函数返回false的时候表示校验未通过。var validata = function(){ if ( username.value === ’’ ){ alert ( ‘用户名不能为空’ ); return false; } if ( password.value === ’’ ){ alert ( ‘密码不能为空’ ); return false; }}var formSubmit = function(){ if ( validata() === false ){ // 校验未通过 return; } var param = { username: username.value, password: password.value } ajax( ‘http:// xxx.com/login’, param );}submitBtn.onclick = function(){ formSubmit();}使用AOP优化代码Function.prototype.before = function( beforefn ){ var __self = this; return function(){ if ( beforefn.apply( this, arguments ) === false ){ // beforefn 返回false 的情况直接return,不再执行后面的原函数 return; } return __self.apply( this, arguments ); }}var validata = function(){ if ( username.value === ’’ ){ alert ( ‘用户名不能为空’ ); return false; } if ( password.value === ’’ ){ alert ( ‘密码不能为空’ ); return false; }}var formSubmit = function(){ var param = { username: username.value, password: password.value } ajax( ‘http:// xxx.com/login’, param );}formSubmit = formSubmit.before( validata ); submitBtn.onclick = function(){ formSubmit();} ...

January 29, 2019 · 3 min · jiezi

创建型模式:原型模式

个人公众号原文:创建型模式:原型模式五大创建型模式之五:原型模式。简介姓名 :原型模式英文名 :Prototype Pattern价值观 :效率第一个人介绍 :Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(来自《设计模式之禅》)又到了一个系列的最后一篇文章了,今天是创建型模式的最后一篇。什么是创建型模式呢?创建型模式是对类的实例化过程进行抽象,使对象的创建和使用分离,从而使代码更加灵活。我们平时使用最多的一种创建对象方式就是 new ABC(),直接通过构造方法来创建一个对象。通过原型模式来创建对象则不用调用构造方法,就可以创建一个对象。下面来揭开它的面纱。你要的故事前几天有出版社的老师邀请写书,鉴于深知自己水平还不足以出书,所以没有合作,还在努力学习,以后有能力有机会再考虑这方面的事情。今天的故事就从出书讲起。我们知道一本新书发版的时候,会复印很多册,如果销售得好,会有很多个印刷版本。我们来了解复印一批书籍这个过程是怎么实现的。小明写下了下面这段代码。public class NoPrototypeTest { public static void main(String[] args) { for (int i = 1; i <= 10; i ++) { Book book = new Book(“娱乐至死”, “尼尔波兹曼”, “社会科学”, “XXXX”); System.out.println(“复印书籍:” + book.getName() + “,第 " + i + " 本”); } }}class Book { private String name; private String author; private String type; private String content; public Book(String name, String author, String type, String content) { this.name = name; this.author = author; this.type = type; this.content = content; System.out.println(“实例化书籍:” + this.name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}// 打印结果:实例化书籍:娱乐至死复印书籍:娱乐至死,第 1 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 2 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 3 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 4 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 5 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 6 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 7 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 8 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 9 本实例化书籍:娱乐至死复印书籍:娱乐至死,第 10 本上面小明的代码复印了 10 本《娱乐至死》,代码逻辑没有问题,有个问题就是复印一本就实例化一次书籍,这个实例化可以减少么?使用原型模式可以实现。小明根据这些提示,重新修改了代码。public class PrototypeTest { public static void main(String[] args) { Book2 book1 = new ConcreteBook(“娱乐至死”, “尼尔波兹曼”, “社会科学”, “XXXX”); System.out.println(“复印书籍:” + book1.getName() + “,第 " + 1 + " 本”); for (int i = 2; i <= 10; i ++) { Book2 book2 = (Book2) book1.clone(); System.out.println(“复印书籍:” + book2.getName() + “,第 " + i + " 本”); } }}/** * 抽象类 /abstract class Book2 implements Cloneable { private String name; private String author; private String type; private String content; public Book2(String name, String author, String type, String content) { this.name = name; this.author = author; this.type = type; this.content = content; System.out.println(“实例化书籍:” + this.name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }}/* * 具体类 */class ConcreteBook extends Book2 { public ConcreteBook(String name, String author, String type, String content) { super(name, author, type, content); }}打印结果:实例化书籍:娱乐至死复印书籍:娱乐至死,第 1 本复印书籍:娱乐至死,第 2 本复印书籍:娱乐至死,第 3 本复印书籍:娱乐至死,第 4 本复印书籍:娱乐至死,第 5 本复印书籍:娱乐至死,第 6 本复印书籍:娱乐至死,第 7 本复印书籍:娱乐至死,第 8 本复印书籍:娱乐至死,第 9 本复印书籍:娱乐至死,第 10 本看,打印结果和第一次实现的结果完全不一样,这一次只实例化了一次,后面复印的书籍都没有实例化。我们看看代码的变化,代码中最最主要的就是 Book2 实现了 Cloneable 接口,这个接口有个 clone() 方法,通过实现这个方法,可以实现对象的拷贝,就是不用调用构造方法,直接通过对内存的拷贝来创建一个新的对象。这就是原型模式的实现方式,通过原型模式可以提高创建对象的效率。代码:Prototype Pattern总结通过原型模式,绕过构造方法创建对象,利用内存直接拷贝对象,提高对象的创建性效率。在有大量的对象创建或者类初始化消耗多资源的场景下可以利用原型模式来优化。当然在实现的过程中,要注意浅拷贝与深拷贝的问题,防止写出 bug,文章主要介绍原型模式,就不详细说这个问题了,留给大家去扩展了解。参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》推荐阅读:创建型模式:单例模式创建型模式:工厂方法创建型模式:抽象工厂创建型模式:原型模式公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

January 28, 2019 · 3 min · jiezi

在JavaScript中理解策略模式

设计模式是: 在面向对象软件过程中针对特定问题的简洁而优雅的解决方案. 通过对封装、继承、多态、组合等技术的反复利用, 提炼出可重复使用面向对象的设计技巧.JavaScript 可以模拟实现传统面向对象语言的设计模式. 事实上也的确如此, 好多的代码 demo 都是沿着这个思路分析的. 看完后心里不免有种一万头????在奔腾, 还顺便飘来了六个字, 走(qu)你(de), 设计模式.然而仅仅是生搬硬套, 未免会失去 JavaScript 的灵活性. 不如溯本求源, 看看这些设计模式到底在传达什么, 然后遵循此点.策略模式定义策略模式: 定义一系列的算法, 把它们一个个封装起来, 并且使它们可以相互替换.字面意思, 就是定义封装多种算法, 且各个算法相互独立. 当然, 也不仅仅是算法. 只要定义一些规则, 经处理后输出我们想要的结果就成. 在此我们称单个封装后的算法为一个策略. 一系列封装后的算法称为一组策略.一个基于策略模式的程序至少由两部分组成. 第一部分是一组策略类, 策略类封装了具体的算法, 并负责具体的计算过程. 第二部分是环境类 Context, Context 接受客户的请求, 随后把请求委托给某一个策略类.这是面向传统面向对象语言中的说法. 在面向对象思想中, 通过对组合, 多态等技术的使用来实现一个策略模式. 在 JavaScript 中, 对于一个简单的需求来说, 这么做就有点大材小用了.所以, 上面的那句话, 我们换种说法就是, 策略模式需要至少两部分, 一部分是保存着一组策略. 另一部分则是如何分配这些策略, 即如何把请求委托给某个/些策略. 其实这也是策略模式的目的, 将算法的使用与算法的实现分离.评级快到年底了, 公司打算制定一个标准用来给员工评级发福利.考核项目等级甲乙丙A100>a>=9090>a>=8080>a>=70B100>b>=9090>b>=8080>b>=70以A、B考核项目来评定甲乙丙等级.现有考核人员:考核项目考核人person_1person_2person_3A809392B857090const persons = [ { A: 80, B: 85 }, { A: 93, B: 70 }, { A: 92, B: 90 }]在策略模式中一部分, 我们提到的分配策略. 要想分配策略, 首先就要知道所有的策略, 只有这样我们才能针对性的委托给某个/些策略. 这, 也是策略模式的一个缺点.常规操作甲乙丙等级对 A、B 的分值要求是不一样的. 所以我们可以这么做:function rating(person) { let a = person.A; let b = person.B; if (a >= 90 && b >= 90) { return ‘甲’; } else if (a >= 80 && b >= 80) { return ‘乙’; } else if (a >= 70 && b >= 70) { return ‘丙’ } else { console.log(‘凭啥级, 还不赶紧卷铺走人’); }}persons.forEach(person => { person.rate = rating(person);})// > persons// [ { A: 80, B: 85, rate: ‘乙’ },// { A: 93, B: 70, rate: ‘丙’ },// { A: 92, B: 90, rate: ‘甲’ } ]策略模式下的评级如果换成策略模式, 第一部分就是保存一组策略. 现在我们以甲乙丙三种定级标准来制定三种策略, 用对象来存贮策略. 考虑到以后可能有 D、E、F 等考核项目的存在, 我们稍微改一下:const strategies = { ‘甲’: (person, items) => { const boolean = items.every(item => { return person[item] >= 90; }); if (boolean) return ‘甲’; }, ‘乙’: (person, items) => { const boolean = items.every(item => { return person[item] >= 80; }); if (boolean) return ‘乙’; }, ‘丙’: (person, items) => { const boolean = items.every(item => { return person[item] >= 70; }); if (boolean) return ‘丙’; }}策略就制定好了. 对象的键对应着策略的名称, 对象的值对应着策略的实现.然而, 我们发现, 任何一个策略都不能单独完成等级的评定.可是, 我们有说一组策略只能选择其中一个么? 为了达成某个目的, 策略组封装了一组相互独立平等替换的策略. 一个策略不行, 那就组合呗. 这也是策略模式另一部分存在的意义, 即如何分配策略.function rating(person, items) { return strategies[‘甲’](person, items) || strategies[‘乙’](person, items) || strategies[‘丙’](person, items)}persons.forEach(person => { person.rate = rating(person, [‘A’, ‘B’])})// > persons// [ { A: 80, B: 85, rate: ‘乙’ },// { A: 93, B: 70, rate: ‘丙’ },// { A: 92, B: 90, rate: ‘甲’ } ]逻辑的转移所有的设计模式都遵循一条原则. 即 “找出程序中变化的地方, 并将变化封装起来”.将不变的隔离开来, 变化的封装起来. 策略模式中, 策略组对应着程序中不变的地方. 将策略组制定好存贮起来, 然后想着如何去分配使用策略.当然, 如何制定策略和如何分配策略之间的关系十分紧密, 可以说两者相互影响.再次看看制定的策略, “找出程序中变化的地方, 并将变化封装起来”, 我们可以再次改造一下.const strategies = { ‘甲’: 90, ‘乙’: 80, ‘丙’: 70,}function rating(person, items) { const level = value => { return (person, items) => { const boolean = items.every(item => { return person[item] >= strategies[value]; }); if (boolean) return value; } } return level(‘甲’)(person, items) || level(‘乙’)(person, items) || level(‘丙’)(person, items)}persons.forEach(person => { person.rate = rating(person, [‘A’, ‘B’])})// > persons// [ { A: 80, B: 85, rate: ‘乙’ },// { A: 93, B: 70, rate: ‘丙’ },// { A: 92, B: 90, rate: ‘甲’ } ]在上面的这种做法中, 我们把制定策略的逻辑挪到了分配策略里了. 所以说, 如何制定策略和如何分配策略, 依情况而定.不过回头在看一看这段代码, 是不是和平时用对象映射的做法很相似.当然, 策略模式的用法还有很多, 最常见的是规则校验.小结总结一下:策略模式至少包括两部分, 制定策略和分配策略.策略模式的目的在于, 将策略制定和策略分配隔离开来.策略制定和策略分配关系密切, 相互影响. ...

January 27, 2019 · 2 min · jiezi

设计模式-桥梁模式

栗子定义抽象公司public abstract class Corp{ // 公司生产内容 protected abstract void produce(); // 销售 protected abstract void sell(); // 赚钱 public void makeMoney(){ // 生产 this.produce(); // 销售 this.sell(); }}上方是模板方法下面是房地产公司public class HouseCorp extends Corp{ // 盖房子 protected void produce(){ // 盖房子 } // 卖房子 protected void sell(){ // 卖房子 } // 赚钱 public void makeMoney(){ super.makeMoney(); // 赚钱 }}服装公司public class ClothesCorp extends Corp{ // 生产衣服 protected void produce(){ // 生产衣服 } protected void sell(){ // 出售衣服 } public void makeMoney(){ super.makeMoney(); // 赚钱 }}最后编写场景public class Client{ public static void main(String[] args){ HouseCorp houseCorp = new HouseCorp(); houseCorp.makeMoney(); ClothesCorp clothesCorp = new ClothesCorp(); clothesCorp.makeMoney(); }}更改企业改头换面,生产另外的产品山寨公司public class IPodCorp extends Corp{ // 生产 protected void produce(){ // 生产 } // 畅销 protected void sell(){ // 畅销 } // 赚钱 public void makeMoney(){ super.makeMoney(); // 赚钱 }}赚钱public class Client{ public static void main(String[] args){ // 房地产 HouseCorp houseCorp = new HouseCorp(); // 挣钱 houseCorp.makeMoney(); // 山寨公司 IPodCorp ipodCorp = new IPodCorp(); ipodCorp.makeMoney(); }}继续更改公司和产品分离,让其之间建立关系// 抽象产品类public abstract class Product{ // 生产 public abstract void beProducted(); // 销售 public abstract void beSelled();}// 房子public class House extends Product{ // 豆腐渣房子 public void beProducted(){ // 生产 } // 销售 public void beSelled(){ // 销售 }}继续public class IPod extends Product{ public void beProducted(){ // 生产 } public void beSelled(){ // 销售 }}下面是抽象公司类public abstract class Corp{ // 定义抽象产品对象 private Product product; // 构造函数 public Corp(Product product){ this.product = product; } // 公司赚钱 public void makeMoney(){ // 生产 this.product.beProducted(); // 销售 this.product.beSelled(); }}定义房地产公司public class HouseCorp extends Corp{ // 定义House产品 public HouseCorp(House house){ super(house); } // 赚钱 public void makeMoney(){ super.makeMoney(); // 赚钱 }}山寨公司public class ShanZhaiCorp extends Corp{ // 产品 public ShanZhaiCorp(Product product){ super(product); } // 赚钱 public void makeMoney(){ super.makeMoney(); // 赚钱 }}最后书写场景public class Client{ public static void main(String[] args){ House house = new House(); // 房地产公司 HouseCorp houseCorp = new HouseCorp(house); // 赚钱 houseCorp.makeMoney(); // 生产产品 ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new IPod()); ShanZhaiCorp.makeMoney(); }}此时在目前状态下,若要生产服装,只需要继承Product类,定义一个服装类即可public class Clothes extends Product{ public void beProducted(){ // 生产服装 } public void beSelled(){ // 卖衣服 }}最后书写场景类public class Client{ public static void main(String[] args){ House house = new House(); // 房地产公司 HouseCorp houseCorp = new HouseCorp(house); // 挣钱 houseCorp.makeMoney(); // 山寨公司生产 ShanZhaiCorp.shanZhaiCorp = new ShanZhaiCorp(new Clothes()); ShanZhai.makeMoney(); }}总结桥梁模式,抽象和实现解耦,需要的时候,将实现注入抽象。 ...

January 26, 2019 · 2 min · jiezi

创建型模式:建造者模式

个人公众号原文:创建型模式:建造者模式五大创建型模式之四:建造者模式。简介姓名 :建造者模式英文名 :Builder Pattern价值观 :专治丢三落四个人介绍 :Separate the construction of a complex object from its representation so that the same construction process can create different representations.将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(来自《设计模式之禅》)今天给大家介绍的是建造者模式。建造者模式的使用场景是:创建复杂的对象。什么才能算复杂对象?如果一个对象只需要通过 new XXX() 的方式创建,那就算是一个简单对象;如果需要 new XXX(),并且还要设置很多属性,那这种就可以称为复杂对象了,因为它的构建过程比较复杂。采用建造者模式,可以把这个复杂的构建过程抽离开,使它不依赖创建者。下面我们通过故事来讲解。你要的故事还记得小时候刚开始学煮汤,不了解怎么煮,第一次是煮了板栗排骨汤,因为板栗比较难熟,所以是跟排骨一起下锅,然后煮了 40 分钟,加了盐就可以出锅啦。第二次煮了冬瓜排骨汤,不懂得冬瓜容易熟,就和排骨一起下锅,煮了也差不多 40 分钟,下了盐和香菜,发现怎么这汤有点浓,冬瓜咋都不见了(全都煮透了),我妈看到这锅汤,才跟我说冬瓜容易熟,得先熬排骨,再放冬瓜进去煮。那时才发现熬汤特么还有这差异。上面是背景哈,接下来我们来实现这个煲汤过程,冬瓜排骨汤是先加排骨,熬制 30 分钟,加冬瓜,熬制 18 分钟,加盐加香菜;板栗排骨汤是先加排骨和板栗,熬制 40 分钟,再加盐。这汤都需要加肉、加菜、熬制、加配料。我们使用下面代码实现这个煲冬瓜排骨汤和板栗排骨汤的过程。public class NoBuilderTest { public static void main(String[] args) { // 熬制冬瓜排骨汤 DongGuaPaiGuSoup dongGuaPaiGuSoup = new DongGuaPaiGuSoup(); // 加排骨 dongGuaPaiGuSoup.addMeat(); // 熬制 30 分钟 dongGuaPaiGuSoup.waitMinute(30); // 加冬瓜 dongGuaPaiGuSoup.addVegetables(); // 熬制 10 分钟 dongGuaPaiGuSoup.waitMinute(10); // 加盐加香菜 dongGuaPaiGuSoup.addIngredients(); // 熬制板栗排骨汤 BanLiPaiGuSoup banLiPaiGuSoup = new BanLiPaiGuSoup(); // 加排骨 banLiPaiGuSoup.addMeat(); // 加板栗 banLiPaiGuSoup.addVegetables(); // 熬制 40 分钟 banLiPaiGuSoup.waitMinute(40); // 加盐 banLiPaiGuSoup.addIngredients(); }}/** * 煲汤接口 /interface Soup { /* 加肉 / void addMeat(); /* 加菜 / void addVegetables(); /* 熬制 / void waitMinute(int minute); /* 加配料 / void addIngredients();}/* * 冬瓜排骨汤 /class DongGuaPaiGuSoup implements Soup { @Override public void addMeat() { System.out.println(“加排骨”); } @Override public void addVegetables() { System.out.println(“加冬瓜”); } @Override public void waitMinute(int minute) { System.out.println(“熬制 " + minute + " 分钟”); } @Override public void addIngredients() { System.out.println(“加盐、加香菜”); }}/* * 板栗排骨汤 /class BanLiPaiGuSoup implements Soup { @Override public void addMeat() { System.out.println(“加排骨”); } @Override public void addVegetables() { System.out.println(“加板栗”); } @Override public void waitMinute(int minute) { System.out.println(“熬制 " + minute + " 分钟”); } @Override public void addIngredients() { System.out.println(“加盐”); }}上面代码简单实现了煲冬瓜排骨汤和板栗排骨汤。煲汤我们要关注的点是:各操作的顺序,是先加肉先煮再加菜,还是肉和菜一起放进锅煮。上面代码中,这个过程是谁控制的?是煲汤的人,所以顺序由煲汤的人决定,甚至有可能忘记放配料啥的,这样子的汤就味道不够好。那怎么去解决这些问题?我们通过建造者模式可以解决上面的 2 个问题:煲汤顺序问题和忘记加配料这种丢三落四行为。我们将这个煲汤顺序从煲汤者分离开来,让煲汤者只需要决定煲什么汤就好,让建造者来保证煲汤顺序问题和防止漏加配料。我们用一个 SoupBuilder 来规范化煲汤过程,方法 buildSoup 给实现者提供一个设置煲汤顺序的地方。因为冬瓜排骨汤和板栗排骨汤熬制的过程不一样,所以分别用 DongGuaPaiGuSoupBuilder 和 BanLiPaiGuSoupBuilder 来具体实现冬瓜排骨汤和板栗排骨汤的熬制过程,也就是消除熬制过程和煲汤者的依赖关系。 Director 则相当于一个菜单,提供为熬汤者来选择熬什么汤。具体代码如下所示。public class BuilderTest { public static void main(String[] args) { Director director = new Director(); // 熬制冬瓜排骨汤 director.buildDongGuaPaiGuSoup(); // 熬制板栗排骨汤 director.buildBanLiPaiGuSoup(); }}/* * 煲汤建造接口 /interface SoupBuilder { void buildSoup(); Soup getSoup();}/* * 冬瓜排骨汤建造者 /class DongGuaPaiGuSoupBuilder implements SoupBuilder { private DongGuaPaiGuSoup dongGuaPaiGuSoup = new DongGuaPaiGuSoup(); @Override public void buildSoup() { // 加排骨 dongGuaPaiGuSoup.addMeat(); // 熬制 30 分钟 dongGuaPaiGuSoup.waitMinute(30); // 加冬瓜 dongGuaPaiGuSoup.addVegetables(); // 熬制 10 分钟 dongGuaPaiGuSoup.waitMinute(10); // 加盐加香菜 dongGuaPaiGuSoup.addIngredients(); } @Override public Soup getSoup() { return dongGuaPaiGuSoup; }}/* * 板栗排骨汤建造者 /class BanLiPaiGuSoupBuilder implements SoupBuilder { BanLiPaiGuSoup banLiPaiGuSoup = new BanLiPaiGuSoup(); @Override public void buildSoup() { // 加排骨 banLiPaiGuSoup.addMeat(); // 加板栗 banLiPaiGuSoup.addVegetables(); // 熬制 40 分钟 banLiPaiGuSoup.waitMinute(40); // 加盐 banLiPaiGuSoup.addIngredients(); } @Override public Soup getSoup() { return banLiPaiGuSoup; }}/* * 生产方 /class Director { private DongGuaPaiGuSoupBuilder dongGuaPaiGuSoupBuilder = new DongGuaPaiGuSoupBuilder(); private BanLiPaiGuSoupBuilder banLiPaiGuSoupBuilder = new BanLiPaiGuSoupBuilder(); /* * 熬制冬瓜排骨汤 / public DongGuaPaiGuSoup buildDongGuaPaiGuSoup() { dongGuaPaiGuSoupBuilder.buildSoup(); return (DongGuaPaiGuSoup) dongGuaPaiGuSoupBuilder.getSoup(); } /* * 熬制板栗排骨汤 */ public BanLiPaiGuSoup buildBanLiPaiGuSoup() { banLiPaiGuSoupBuilder.buildSoup(); return (BanLiPaiGuSoup) banLiPaiGuSoupBuilder.getSoup(); }}通过用建造者实现,是不是保证了熬制汤的顺序并且一定会加够料?感受一下其中的奥秘吧。代码:Builder Pattern总结通过建造者模式,可以把本来强依赖的东西解绑,不仅仅解决依赖问题,还提高了封装性,让使用者不用明白内部的细节,用上面的例子说就熬汤不用关心怎么熬制的过程,就像我们想喝冬瓜排骨汤,告诉妈妈,妈妈熬制,我们并不知道是怎么熬制的。参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》推荐阅读:创建型模式:单例模式创建型模式:工厂方法创建型模式:抽象工厂公众号之设计模式系列文章希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号:LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。 ...

January 26, 2019 · 2 min · jiezi

设计模式-享元模式

栗子使用工厂方法,表现层通过工厂方法创建对象,再传递给业务层,持久层,最后保存到数据库中。报考信息public class Signinfo{ // 报名ID private String id; // 考试地点 private String location; // 科目 private String subject; // 邮寄地址 private String postAddress; public String getId(){ return id; } public void setId(String id){ this.id = id; } public String getLocation(){ return location; } public String getSubject(){ return subject; } public void setSubject(String subject){ this.subject = subject; } public String getPostAddress(){ return this.postAddress; } public void setPostAddress(String postAddress){ this.postAddress = postAddress; }}报考信息工厂,批量生产报考信息对象public class SiginfoFactory{ // 报名信息的对象工厂 public static Signinfo getSigninfo(){ return new Signinfo(); }}最后书写场景public class Client{ public static void main(String[] args){ // 从工厂中获得对象 Signinfo signinfo = SigninfoFactory.getSigninfo(); // 进行其他业务处理 }}垃圾回收机制jvm有垃圾回收机制,即自动内存管理。其中堆是所有线程都共享的,而栈是每个线程都各自拥有的。程序有程序计数器,当线程超过cpu数量,或者cpu内核数量的时候,线程根据时间片轮询抢夺cpu时间资源,即每个线程都需要一个独立的程序计数器记录正在执行的字节码的指令地址。虚拟机栈,虚拟机栈是java方法执行的内存模型,为线程私有,每个方法在执行的时候,会创建一个栈帧,这个栈帧保存着局部变量表,操作数栈,动态链接,方法出口,每个方法的调用,都代表着一个栈帧从入栈到出栈的过程。方法调用会入栈,方法返回会出栈关于java堆,垃圾收集器的主要管理区域,即GC堆,java垃圾回收算法有引用计数法,通过判断对象的引用数量决定对象是否可以被回收,当引用为0的对象,可以被当做垃圾回收,当对象之间相互引用的时候,由于两者引用计数都不为0,所以不能使用gc进行清除。可达性分析算法,程序把引用关系看做一张图,从节点往下搜索,当对象没有任何引用链的时候,会证明不可达,此时进行回收垃圾收集算法,对象先判断是否可以进行回收,先标记再清除,从跟集合扫描,对存活对象标记,标记完成以后,对未标记的进行回收。复制算法。将可用内存分大小相等两块,这一块用完,将存活的复制到另外一块上,再把已经使用的内存空间一次清除掉。即,将堆空间分为两块,一块为新生代,一块为老年代,在进行回收的时候,会把存活的对象,复制到新生代中,将老年代清空。内存泄露使用HashMap,Vector等集合类的静态使用容易出现内存泄露,因为这些静态变量的生命周期和应用程序一致。即各种资源的连接,网络连接,IO连接没有被close关闭,更改当并发数增多的时候,每个线程会创建对象,并发数越多,线程数越多,此时会造成内存的疯狂占用,造成内存泄露。解决:对象池。使用一个共享池来解决问题。代码如下public class Signinfo4Pool extends Signinfo{ // 定义一个key private String key; // 构造函数获得标志 public Signinfo4Pool(String _key){ this.key = _key; } public String getKey(){ return key; } public void setkey(String key){ this.key = key; }}下面书写带对象池的工厂类public class SigninfoFactory{ // 池容器 private static HashMap<String, Signinfo> pool = new hashMap<String, Signinfo>(); // 对象工厂 @Deprecated // 对原先代码进行修改的时候,需要加上Deprecated,表明这个方法已经废弃。不在建议使用,不建议删除,因为如果有其他继续使用这个方法的时候,将会导致出现不可预知的问题。用于向下兼容 public static Signinfo(){ return new Signinfo(); } // 获得对象 public static Signinfo getSigninfo(String key){ Signinfo result = null; // 当key值存在的时候,从对象池中获得该对象。当key值不存在的时候,创建对象,并放入对象池中。 if(!pool.containsKey(key)){ result = new Signinfo4Pool(key); pool.put(key, result); }else{ result = pool.get(key); } return result; } }最后书写场景类public class Client{ public static void main(String[] args){ // 初始化对象池 for(int i = 0 ; i < 4; i++){ String subject = “科目” + i; // 初始化地址 for(int j = 0; j < 30; j++){ String key = subject + “地点” + j; SigninfoFactory.getSigninfo(key); } } // 从池中获取对象然后进行处理。 Signinfo singinfo = SignInfoFactory.getSignInfo(“科目1地点1”); }}总结即,将共性的内容,提取出来,然后在新建一个子类,然后在子类中预留出外部访问的。上方的栗子为key。然后在工厂模式中,String为key,value的值为key对应的对象池中创建的对象。ps 在上方的栗子中id作为附属的,即,对象的动态信息。池 + 工厂 通过新建一个对象池,该池内的对象有HashMap来进行保存,然后通过工厂,输入key值,获取到对象。核心在于创建出业务需要的对象,然后在运行的时候,直接使用该对象。因为堆是所有线程所共享的。线程安全由于java中堆是所有线程所共享的,所以当共享池中的对象数不够的时候,会出现线程安全的问题,即,多个线程,共同访问一个对象,同时修改造成数据的错误。即,在使用享元模式的时候,对象要尽可能的多,直到业务需求全部满足。还要注意线程安全的问题,当一个线程外部状态/内部状态关于外部状态和内部状态,其中内部状态不可更改,外部状态可更改,但是多线程的时候会出现很严重的问题,即线程不安全,当多个线程共同访问,操作外部状态的时候,会出现线程不安全。至今不知道怎么解决。继续扩展此时,新建一个类,用于保存key值,替代原先的String方式。再写static类型的里面保存的HashMap,用于保存当前池中的对象。需要使用的时候,直接新将外部类,将内容set进入。工厂内需要调用状态类的方法,生成String和HashMap中的key进行对比。若已经生成,直接返回该对象,否则不返回该对象总结享元模式 = 工厂模式 + 池 ...

January 25, 2019 · 2 min · jiezi

从 IM 通信 Web SDK 来看如何提高代码可维护性与可扩展性

本文内容概述在架构设计和功能开发中,代码的可维护性和可扩展性一直是工程师不懈的追求。本文将以我工作中开发的 IM 通信服务 SDK 作为示例,和大家一起探讨下前端基础服务类业务的代码中对可维护性和可扩展方面的探索。本文不涉及具体的代码和技术相关细节,如果想了解 IM 长连接相关的技术细节,可以阅读我之前的文章:WebSocket系列之基础知识入门篇WebSocket系列之JavaScript数字数据如何转换为二进制数据WebSocket系列之JavaScript字符串如何与二进制数据间进行互相转换WebSocket系列之二进制数据设计与传输WebSocket系列之如何建立和维护可靠的连接背景介绍大象 SDK 是美团生态中负责 IM 通信服务的基础服务。作为 IM 通信服务的 Web 端载体,我们对不同的业务线提供不同的功能来满足特定的需求,同时需要支持 PC、Web、移动端H5、微信小程序等各个平台。不同的业务方需求和不同的平台对 Web SDK 的功能和模块要求都不相同,因此在整个 Web SDK 中有许多部分存在需要适配多场景的情况。处理这种常见的场景,我们一般有以下几个思路:针对不同的场景单独开发不同的基础服务代码。这种操作灵活性最强,但是成本也是最高的,如果我们需要面对 M 个业务需求和 N 个平台,我们就需要有 M * N 套代码。这个对于技术人员来说,基本上是一个不可能接受的情况。将所有的代码全部聚合到一个业务模块中,通过内部的 IF ELSE 判断逻辑来自动选择需要执行的代码逻辑。这种方案不会出现相同代码重复编写的情况,同时也兼顾了灵活性,看上去是一个不错的选择。但是我们仔细一想就会发现,所有的代码都堆积到一起,在后期会遇到大量的判断逻辑,在可维护性上来看是一个巨大的灾难。同时,我们所有的代码都放到一起,这会导致我们的包体积越来越大,而其他业务在使用相关功能时,也会引入大量无用代码,浪费流量。那么,我们在既需要兼顾可维护性,有需要保证开发效率的情况下,我们应该如何去进行相关业务的架构设计呢?核心原则在我的设计理念中,有这么几个原则需要遵守:针对接口规范编程,而不针对特定代码编程(即设计模式中的策略模式)。我们在进行架构设计时,优先判断各个功能和模块中流转的数据格式和交互的数据接口规范,这样我们可以保证在进行特定代码编写的时候,只针对具体格式进行数据处理,而不会设计到数据内容本身。各模块权责分明,宽进严出。每个模块都是单一全责,暴露特定数据格式的 API,处理约定好数据格式的内容。提供方案供用户选择,而不帮用户做决策。我们不去判断用户所在环境、选择功能,而是提供多个选择来让用户主动去做这个决策。具体实践上面的原则可能比较抽象,我们来看几个具体的场景,大家就能够对这个有一个特定的概念。连接模块设计(长连接部分)连接模块包含长连接和短连接部分,我们在这里就用长连接部分来进行举例,短连接部分我们可以按照类似的原则进行设计即可。在设计长连接部分时,我们需要考虑的是:连接策略与切换策略。总的来说就是我们需要在什么时候使用哪一种长连接。首先,我们以浏览器端为例,我们可以选择的长连接有:WebSocket 和长轮询。这个时候,我们可能首先以 WebSocket 优先,而长轮询作为备选方案来构成我们的长连接部分。因此,我们可能会在代码中直接用代码来实现这个方案。相关伪代码如下:import WebSocket from ‘websocket’;import LongPolling from ’longPolling’;class Connection { private _websocket; private _longPolling; constructor() { this._websocket = new WebSocket(); this._longPollong = new LongPolling(); } connect() { this.websocket.connect(); // 只表达相关含义用于说明 if (websocket.isConnected) { this.websocket.send(message); } else { this.longPolling.connect(); } }}在正常情况下来看,我们发现这个代码没有什么问题。但是,如果我们的需求发生了某些变化呢?比如我们现在需要在某些特定的场景下,只开启长轮询,而不开启 WebSocket 呢(比如在 IE 浏览器里面)?之前的做法是在构造器的时候,传递一个参数进去,用来控制我们是不是开启 WebSocket。因此,我们的代码会变成以下的样子。class Connection { private _useWebSocket; private _websocket; private _longPolling; constructor({useWebSocket}) { this._useWebSocket = useWebSocket; this._websocket = new WebSocket(); this._longPollong = new LongPolling(); } connect() { if (this._useWebSocket) { this.websocket.connect(); // 只表达相关含义用于说明 if (websocket.isConnected) { this.websocket.send(message); } else { this.longPolling.connect(); } } else { this._longPolling.connect(); } }}现在,我们通过增加一个判断参数,对connect函数进行了简单的改造,满足了在特定场景下的指使用长轮询的需求。很不幸,我们的问题又来了,我们在针对移动端 H5 的场景下,我们需要一个只要 WebSocket 连接,而不需要长轮询。那么,根据我们之前的方式,我们可能又需要在增加一个新的参数useLongPolling。这个代码示例我就不增加了,大家应该能够想象出来。在线上运行了一段时间后,新的需求又来了,我们需要在微信小程序里面支持 IM 的长连接。那么,根据我们之前的思路,我们需要在私有属性和connect方法中增加一堆判断逻辑。具体示例如下:import WebSocket from ‘websocket’;import LongPolling from ’longPolling’;import WXWebSocket from ‘wxwebsocket’;class Connection { private _websocket; private _longPolling; private _wxwebsocket; constructor() { // 如果在微信小程序容器中 if (isInWX()) { this._wxwebsocket = new WXWebSocket(); } else { this._websocket = new WebSocket(); this._longPollong = new LongPolling(); } } connect() { if (isInWx()) { this._wxwebsocket.connect(); } else { this.websocket.connect(); // 只表达相关含义用于说明 if (websocket.isConnected) { this.websocket.send(message); } else { this.longPolling.connect(); } } }}从这个例子,大家应该可以发现相关的问题了,如果我们再支持百度小程序、头条小程序等更多的平台,我们就会在我们的判断逻辑里面加更多的逻辑,这样会让我们的可维护性有明显的下降。现在有一些类库可以支持多平台的接口统一(大家去GitHub上面找一下就可以发现),那么为什么我没有用相关的产品呢?这是因为 SDK 作为一个基础服务,对包大小比较敏感,同时用到的需要兼容 API 并不多,所以我们自己做相关的兼容比较合适。那么,我们应该如何设计这个方案,从而解决这个问题呢。让我们回顾下我们的设计理念。针对接口规范编程,而不针对特定代码编程。各模块权责分明,宽进严出。提供方案供用户选择,而不帮用户做决策。通过这些设计理念,我们来看下具体的做法。三个设计理念我们需要组合使用。首先是针对结构规范编程。我们来看下具体的用法。首先我们定义一个长连接的接口如下:export default interface SocketInterface { connect(url: string): void; disconnect(): void; send(data: any[]): void; onOpen(func): void; onMessage(func): void; onClose(func): void; onError(func): void; isConnected(): boolean;}有了这个长连接的接口类型后,我们可以让 WebSocket 和长轮询两个模块都实现这个接口。因此,他们就有了统一的 API。有了统一的 API 之后,我们就可以将连接策略中的操作“泛化”,从操作具体的连接方式转换为操作被选中的连接方式。其次,根据我们的各模块全责分明的原则,我们的连接模块应该只控制我们的连接策略,并不需要关心她使用的是 WebSocket 还是长轮询,还是说微信小程序的 API。道理很简单,但是具体我们应该怎么来实践呢?我们来看下下面这个示例:class Connection { private _sockets = []; private _currentSocket; constructor({Sockets}) { for (let Socket of Sockets) { let socket = new Socket(); socket.onOpen(() => { for (let socket of this._sockets) { if (socket.isconnected()) { this._currentSocket = socket; } else { socket.disconnect(); } } }); this._sockets.push(socket); } } connect() { for (let socekt of this._sockets) { socket.connect(); } }}通过上面这个示例大家可以看到,我们泛化了每一个连接方式的差异,转为用统一的接口规范来约束相关的模块。这样带来的好处是,我们如果需要兼容 WebSocket 和长轮询时,我们可以把这两个的构造函数传递进来;如果我们需要支持微信小程序,我们也只需要将微信小程序的 API 封装一次,我们就可以得到我们需要的模块,这样可以保证我们的连接模块只负责连接,而不去关心它不该关心的兼容性问题。那么由用户就会问了,那我们是在哪一层来判断传入的参数到底是哪些呢?是在这个模块的上一层吗?这个问题很简单,还记得我们的第三个规则是什么吗?那就是提供方案供用户选择,而不帮用户做决策。因此,我们在构建长连接部分的时候,我们就在 Webpack 里面定义一些常量用于判断我们当前构建时,我们生产的的包是用于什么场景。具体示例如下:import Connection from ‘connection’;import WebSocket from ‘websocket’;import LongPolling from ’longPolling’;import WXWebSocket from ‘wxwebsocket’;class WebSDK { private _connection; constructor() { if (CONTAINER_NAME === ‘WX’) { this._connection = new Connection({Sockets: [WXWebSocket]}); } if (CONTAINER_NAME === ‘PC’) { this._connection = new Connection({Sockets: [WebSocket, LongPolling]}); } if (CONTAINER_NAME === ‘H5’) { this._connection = new Connection({Sockets: [WebSocket]}); } }}我们通过在 Webpack 中定义 CONTAINER_NAME 这个常量,我们可以在打包时构建不同的 Web SDK 包。在保证对外暴露 API 完全一致的情况下,业务方可以在不同的容器内,采用对应的打包方式,引入不同的 Web SDK 的包,同时不需要改动任何代码。可能有人会问了,这个方式看上去其实和之前的方式没有什么不同,只是把这个 IF ELSE 的逻辑移动到了外面。但是,我可以告诉大家,这里有两个明显的优势:我们可以抽象单独的模块去管理和维护这个独立的判断逻辑,它不会和我们的长连接部分代码进行耦合。我们可以在打包过程中使用 tree-shaking,这样我们可以让我们的 Web SDK 构建的包中,不会出现我们不需要的模块的代码。消息流处理上面的长连接部分,我们看到了三个原则的使用。接下来我们来看下我们如何使用这个原则进行数据流的处理。在 IM 场景中,我们会遇到许多类型的消息。我们以微信公众号为例,我们会碰到单聊(单人-单人)、群聊(单人-群组)、公众号(单人-公众号)等聊天场景。如果我们需要去计算消息的未读数,同时用消息来更新左侧的会话列表,我们就需要三套几乎完全一样的逻辑。那么,我们有没有什么更优的方法呢。很明显,我们可以根据上面介绍的原则,定义一个消息接口。interface MessageInterface { public fromId: string; public toId: string; public fromName: string; public messageType: number; public messageBody; public uuid: string; public serverId: string; public extension: string;}通过之前的例子,大家应该可以理解,我们现在的所有业务逻辑,比如更新未读数、更新会话列表的预览消息时,我们就只需要针对整个消息接口里面的数据进行处理。这样的话,我们的处理流程就会变成一个流水线作业,我们只负责处理特定逻辑的数据,而不管具体的数据内容是什么样子的。因此,如果我们新增一类会话类型,比如客服消息,我们也可以按照上面这个接口去实现客服消息类,复用原来的逻辑,而不需要重新实现一套完整的代码。我们的在一开始就需要对数据进行转换,这样才能够保证我们在内部流转时不会犹豫数据格式不同导致代码维护性变差。需要注意的是,根据我们的各模块权责分明,宽进严出原则,我们在像其他模块输出时,我们也需要保证我们只输出这一种格式的数据,而接受的数据,我们应该尽最大的努力去适应各种场景。可能有人会问,我们内部自己规定使用那个系统就可以,控制了严出了,我们自然就不用处理宽进了。但是,你写的代码和模块很有可能会和其他人一起维护,这个时候,你只能从规范上面来约束他,而不能控制他。因此,我们在接收其他非同一开发模块的数据时,我们可能会遇到一些异常情况。这个时候如果我们对宽进有做处理,也能够保证该模块可以正常运行。有了之前的经验,大家对这个示例应该很好理解,我就不多做介绍了。总结这一篇文章没有介绍什么代码层面的东西,而是和大家一起交流了一下,我在日常工作中遇到的一些可能的问题,以及关于设计模式相关的应用场景。如果我们需要作为一个基础服务提供方,需要让自己的代码有扩展性和可维护性,我们需要:面对接口规范编程。单一全责、宽进严出。不帮用户做决策。当然,在用户产品层面,可能上面的设计有部分相同的地方,也有部分不同的地方,有时间的话,我会在后面再和大家进行分享。大家如果有兴趣的话可以在评论区发表下自己观点,也可以在评论里面留言进行讨论,也欢迎大家发表自己的观点。作者介绍与转载声明黄珏,2015年毕业于华中科技大学,目前任职于美团基础研发平台大象业务部,独立负责大象 Web SDK 的开发与维护。本文未经作者允许,禁止转载。 ...

January 25, 2019 · 2 min · jiezi

访问者模式

首先抽象员工public abstract class Employee{ // 代表男性 public final static int MALE = 0; // 代表女性 public final static int FEMALE = 1; // 有工资 private String name; // 薪水 private int salary; // 性别 private int sex; // get/set public String getName(){ return name; } public void setName(String name){ this.name = name; } public int getSalary(){ return salary; } public void setSalary(int salary){ this.salary = salary; } public int getSex(){ return sex; } public void setSex(int sex){ this.sex = sex; } // 员工信息输出 public final void report(){ String info = “姓名” + this.name + “\t”; info = info + “性别” + this.sex + “\t”; info = info + “薪水” + this.salary + “\t”; info = info + this.getOtherinfo(); System.out.println(info); } // 拼装 protected abstract String getOtherinfo();}下面是普通员工public class CommonEmployee extends Employee{ // 工作内容 private String job; public String getJob(){ return job; } public void setJob(String job){ this.job = job; } protected String getOtherinfo(){ return “工作: " + this.job + “\t”; }}管理层public class Manager extends Employee{ // 职责 private String performance; public String getPerformance(){ return performance; } public void setPerformance(String performance){ this.performance = performance; } protected String getOtherinfo(){ return “业绩” + this.performance + “\t”; }}最后场景public class Client{ public static void main(String[] args){ for(Employee emp:mockEmployee()){ emp.report(); } } public static List mockEmployee(){ List empList = new ArrayList(); // 产生员工 CommonEmployee zhangSan = new CommonEmployee(); zangSan.setJon(“编写程序”); zangSan.setName(“张三”); zangSan.setSalary(1800); zangSan.setSex(Employee.MALE); empList.add(zangSan); // 产生员工 CommonEmployee liSi = new CommonEmployee(); liSi.setJob(“美工”); liSi.setName(“李四”); liSi.setSalary(1900); liSi.setSex(Employee.FEMALE); empList.add(liSi); // 产生经理 Manage wangWu = new Manger(); wangWu.setName(“王五”); wangWu.setPerformance(“负值”); wangWu.setSalary(18750); wangWu.setSex(Employee.MALE); empList.add(wangWu); return empList; }}改造如下先定义访问者接口public interface IVisitor{ // 定义普通员工 public void vsit(CommonEmployee commonEmployee); // 定义访问部门经理 public void visit(Manager manager);}访问者实现public class Visitor implements IVisitor{ // 访问普通员工,打印报表 public void visit(CommonEmployee commonEmployee){ System.out.println(this.getCommonEmployee(commonEmployee)); } // 访问部门经理 public void visit(Manager manager){ System.out.println(this.getManagerinfo(manager)); } // 组装基本信息 private String getBasicinfo(Employee employee){ String info = “姓名” + employee.getName() + “\t”; info = info + “性别” + (employee.getSex() == Employee.FEMALE?“女”,“男”); info = info + “薪水” + employee.getSalary() + “\t”; return info; } // 组装普通员工信息 private String getCommonEmployee(CommonEmployee commonEmployee){ String basicinfo = this.getBasicinfo(commonEmployee); String otherinfo = “工作” + commonEmployee.getJob() + “\t”; return basicinfo + otherinfo; } }继续书写抽象员工类public abstract class Employee{ public final static int MALE = 0; public final static int FEMALE = 1; // 工资 private String name; // 薪水 private int salary; // 性别 private int sec; // get/set public String getName(){ return name; } public void setName(String name){ this.name = name; } public int getSalary(){ return salary; } public void setSalary(int salary){ this.salary = salary; } public int getSex(){ return sex; } public void setSex(int sex){ this.sex = sex; } // 定义访问者访问 public abstract void accept(IVisitor visitor);}普通员工public class CommonEmployee extends Employee{ // 工作内容 private String job; // 获得job public String getJon(){ return job; } // 设置job public void setJob(String job){ this.job = job; } // 允许访问者访问 @Override public void accept(IVsitor visitor){ visitor.visit(this); }}场景类public class Client{ public static void main(String[] args){ // 开始循环列表 for(Employee emp:mockEmployee()){ // 将访问者传入,此时accept将会调用visit方法,将this传入 emp.accept(new Visitor()); } } public static List mockEmployee(){ List empList = new ArrayList(); // 产生员工 CommonEmployee zhangSan = new CommonEmployee(); // 设置属性 zhangSan.setJon(“编写程序”); zhangSan.setName(“张三”); zhangSan.setSalary(1800); zhangSan.setSex(Employee.MALE); empList.add(zhangSan); // 产生员工 CommonEmployee liSi = new CommonEmployee(); liSi.setJob(“美工”); liSi.setSalary(“李四”); liSi.setSalary(1900); liSi.setSex(Employee.FEMALE); // 入数组 empList.add(liSi); // 产生经理 Manager wangWu = new Manager(); wangWu.setName(“王五”); wangWu.setPerformance(“负数”); wangWu.setSalary(18750); wangWu.setSex(Employee.MALE); empList.add(wangWu); return empList; }}扩展统计功能汇总和报表,经常使用统计功能。即,一堆计算公式,生产出一个报表。统计公式员工的工资总额// 定义抽象访问者public interface IVisitor{ // 定义可以访问的普通员工 public void visit(CommonEmployee commonEmployee); // 再次定义访问部门经理 public void visit(Manager manager); // 统计员工工资总和 public int getTotalSalary();}定义访问者// public class Visitor implements IVisitor{ // 这里实现接口}最后编写场景类public class Client{ public static void main(String[] args){ // 定义一个访问者 // 展示报表的访问者 ISVisitor showVisitor = new Visitor(); // 汇总报表的访问者 ITotalVisitor totalVisitor = new TotalVisitor(); // 遍历,其中mockEmployee是从持久层传来的数据 for(Employee emp:mockEmployee()){ // 对数据进行处理, // 代执行访问着的visit方法。由于数据是保存在emp的,所以需要将this传入 emp.accept(showVisitor); emp.accept(totalVisitor); } System.out.println(”"); }}双分派单分派:处理一个操作根据请求着的名称和接受到的参数决定静态绑定,动态绑定,依据重载,覆写实现。栗子// 定义角色接口,实现类public interface Role{ // 演员扮演的角色}public class KungFuRole implements Role{ // 武功第一的角色}public class idiotRole implements Role{ // 弱智角色}下面定义抽象演员public abstract class AbsActor{ // 演员能够扮演的角色 public void act(Role role){ // 演员扮演的角色 } // 扮演功夫戏 public void act(KungFuRole role){ // 扮演功夫戏 }}// 青年演员,老年演员public class YoungActor extends AbsActor{ // 年轻演员喜欢的功夫戏 public void act(KungFuRole role){ }}// 老年演员喜欢的功夫戏public class OldActor extends AbsActor{ // 扮演功夫角色 public void act(KungFuRole role){ // 扮演功夫角色 }}编写场景public class Client{ public static void main(String[] args){ // 定义一个演员 AbsActor actor = new OldActor(); // 定义一个角色 Role role = new KungFuRole(); // 开始演戏 actor.act(role); actor.act(new KungFuRole()); }}重载在编译期间决定要调用那个方法。静态绑定,是重写的时候就断定要绑定那个,例如定义年轻演员的时候,重写的act方法,此时为静态绑定了KungFuRole,动态绑定呢,act方法,只有在运行的时候才能判断是和那个绑定一个演员可以扮演多个角色,如何实现呢,使用访问者模式public interface Role{ // 演员扮演的角色 public void accept(AbstActor actor);}public class KungFuRole implements Role{ // 角色 public void accept(Abstract actor){ actor.act(this); }}public class ldiotRole implements Role{ // 弱智角色 public void accept(AbsActor actor){ actor.act(this); }}书写场景类public class Clicent{ public static void main(String[] args){ // 定义演员 AbsActor actor = new OldActor(); // 定义角色 Role role = new KungFuRole(); // 开始演戏 role.accept(actor); }}总结在上面的栗子中,角色只有在运行期间才能判断由那个演员执行,演员事先定义和那个角色绑定,所以角色使用接口,演员使用抽象类。接口,抽象类 接口呢 是在运行的时候才能发现,所以使用动态绑定,抽象类适合使用静态绑定。啊哈,这就是接口和抽象类的区别。访问者模式的核心在于定义一个方法,就像开启一个门,让访问者进入,然后在将其信息传递给访问者,由访问者执行需要产生的内容。应用过滤展示,报表,ui展示,过滤器,拦截器 ...

January 24, 2019 · 4 min · jiezi

状态模式

模拟电梯定义电梯接口public interface ILift{ // 开门 public void open(); // 关门 public void close(); // 能运行 public void run(); // 停 public void stop();}实现public class Lifi implements ILift{ // 关闭 public void close(){ // 关门 } // 开门 public void open(){ // 开门 } // 运行 public void run(){ // 运行 } // 停止 public void stop(){ // 停止 }}书写场景类public class Client{ public static void main(String[] args){ ILift lift = new Lift(); // 开门 lift.open(); // 关门 lift.close(); // 运行 lift.run(); // 停止 lift.stop(); }}更改目前不清楚电梯的状态,所以增加电梯的状态public interface ILift{ // 四个状态 // 开门 public final static int OPENING_STATE = 1; // 关门 public final static int CLOSEING_STATE = 2; // 运行 public final static int RUNNING_STATE = 3; // 停止 public final static int STOPPING_STATE = 4; // 设置状态 public void setState(int state); // 下方相同}// 实现类public class Lift implements ILift{ private int state; public void setState(int state){ this.state = state; } // 当电梯门关闭 public void close(){ // 关闭 switch(this.state){ case OPENING_STATE; // 关门 this.closeWithoutLogic(); // 修改状态 } } // 下方同理}修改对电梯的状态进行封装,当调用的时候,直接自动改变电梯的状态。即,迪米特法则即,外界不需要知道电梯的状态,为一个封装好的。public abstract class LiftState{ // 定义环境角色 protected Context context; // 设置状态 public void setContext(Context _context){ this.context = _context; } // 开门 public abstract void open(); // 关门 public abstract void close(); // 运行 public abstract void run(); // 停 public abstract void stop();}开门状态public class OpenningState extends LiftState{ // 关门 @Override public void close(){ // 状态更改 super.context.setLiftState(Context.closeingState); // 委托执行 super.context.getLiftState().close(); } // 打开电梯门 @Override public void open(){ // 开门 } // 运行 @Override public void run(){ // 门开,禁止运行 } // 关门 @Override public void stop(){ // 开门的状态即停止 }}// 环境类,用于封装当前电梯的状态public class Context{ // 定义电梯状态 public final static OpenningState openningState = new OpenningState(); // 剩下三个状态相同 // 当前状态 private LiftState liftState; // 获得状态 public LiftState getLiState(){ return liftState; } // 设置状态 public LiftState setLiftState(LiftState liftState){ this.liftState = liftState; // 通知到当前的状态 this.liftState.setContext(this); }}剩下的几个相同// 书写场景类public class Client{ public static void main(String[] args){ // 此时定义一个电梯的状态 Context context = new Context(); // 此时电梯进入关闭状态 // 将关闭状态,保存进入context中 // 进行通知,将当前状态的父类设置为context context.setLiFtState(new ClosingState()); // 代执行ClosingState context.open(); // 发生关闭状态的时候, context.close(); context.run(); context.stop(); }}总结状态模式的核心在于封装,将对象的状态进行封装。在于通知,当状态发生改变的时候,进行通知。 ...

January 24, 2019 · 2 min · jiezi

Java设计模式之单例模式,这是最全最详细的了

一、单例模式作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问入口1、单例模式的常用1.Windows的任务管理器2.Windows的回收站,也是一个单例应用3.项目中的读取配置文件的对象4.数据库的连接池5.Servlet中的Application Servlet6.Spring中的Bean默认也是单例的7.SpringMVC Struts中的控制器2、单例模式的优点1.由于单例模式只生成一个实例,减少了系统给的性能开销,当一个对象需要产生时,当时消耗的资源较多。那么产生对象时构建的方式就可以通过单例去构建。2.单例模式存在全局访问点,所以可以优化共享资源访问。3、常见的单例模式的构建方法1.饿汉式:线程安全 调用率高 但是不能延迟加载2.懒汉式:线程安全 调用率不高 但是可以延迟加载3.双重检测(double check )4.静态内部类(线程安全 可以延迟加载)5.枚举单例 线程安全 不可以延迟加载二、代码案例展示1、饿汉式/** * 饿汉式: * 类只要被加载就会被加载全局变量,所以饿汉式,会被及时加载。(没有懒加载 ) * 并且存在天然的线程安全问题。 * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public class SingleHungry { //提供静态的全局变量 作为访问该类实例的入口 private static SingleHungry sh = new SingleHungry(); /* * 构造器私有 无法创建对象 / private SingleHungry(){ } /* * 对外提供get方法获取 该类的实例 * @return / public static SingleHungry getInstance(){ return sh; } } 2、懒汉式/* * 懒汉式: * 全局变量初始化放到了实例化方法中,延迟产生对象。 * 但是当多个线程统一访问时,有可能出现线程不安全的情况。需要优化。 * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public class SingleLazy implements Serializable{ //提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载 private static SingleLazy sh = null; /* * 构造器私有 无法创建对象 / private SingleLazy(){ System.out.println(“构造函数被调用了”); } /* * 对外提供get方法获取 该类的实例 * @return * @throws InterruptedException / public static synchronized SingleLazy getInstance() { if(sh==null){ sh = new SingleLazy(); } return sh; } } 上海尚学堂java培训 shsxt.com3、双重检测/* * 懒汉式: * 全局变量初始化放到了实例化方法中,延迟产生对象。 * 但是当多个线程统一访问时,有可能出现线程不安全的情况。需要优化。 * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public class SingleLazy4 { //提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载 private volatile static SingleLazy4 sh = null; /* * 构造器私有 无法创建对象 / private SingleLazy4(){ System.out.println(“被调用了”); } /* * 双重校验锁式(也有人把双重校验锁式和懒汉式归为一类)分别在代码锁前后进行判空校验 * ,双重校验锁式是线程安全的。然而,在JDK1.5以前,DCL是不稳定的,有时也可能创建多个实例, * 在1.5以后开始提供volatile关键字修饰变量来达到稳定效果。 * 双重校验锁DCL(double checked locking) * @return * @throws InterruptedException / public static SingleLazy4 getInstance() { if(sh == null){ synchronized(SingleLazy4.class){ if(sh == null){ sh = new SingleLazy4(); //return singleton; //有人提议在此处进行一次返回 } //return singleton; //也有人提议在此处进行一次返回 } } return sh; } } 上海尚学堂Java培训 shsxt.com 获取更多java学习资料4、静态内部类/* 静态内部类 * * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public class SingleInner { / *静态内部类式和饿汉式一样,同样利用了ClassLoader的机制保证了线程安全; 不同的是,饿汉式在Singleton类被加载时(从代码段3-2的Class.forName可见) 就创建了一个实例对象,而静态内部类即使Singleton类被加载也不会创建单例对象, 除非调用里面的getInstance()方法。因为当Singleton类被加载时 ,其静态内部类SingletonHolder没有被主动使用。只有当调用getInstance方法时, 才会装载SingletonHolder类,从而实例化单例对象。 这样,通过静态内部类的方法就实现了lazy loading,很好地将懒汉式和饿汉式结合起来, 既实现延迟加载,保证系统性能,也能保证线程安全 / private static class SingleInnerHolder{ private static SingleInner instance = new SingleInner(); } private SingleInner(){ System.out.println(“我被调用了”); } public static SingleInner getInstance(){ return SingleInnerHolder.instance; } } 5、枚举单例/ * jvm提供底层保证 * 不可能出现序列化、反射产生对象的漏洞 但是不能做到延迟加载 在外部,可以通过EnumSingleton.INSTANCE.work()来调用work方法。默认的枚举实例的创建是线程安全的 、,但是实例内的各种方法则需要程序员来保证线程安全。 总的来说,使用枚举单例模式,有三个好处: 1.实例的创建线程安全,确保单例。2.防止被反射创建多个实例。3.没有序列化的问题。 * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public enum SingleEnum { //实例化对象 INSTANCE; / * 对象需要执行的功能 / void getInstance(){ } } 上海尚学堂java培训 shsxt.com6、反射/序列化,获取对象,以及防止方式import java.io.ObjectStreamException; import java.io.Serializable; / * 懒汉式: * 全局变量初始化放到了实例化方法中,延迟产生对象。 * 但是当多个线程统一访问时,有可能出现线程不安全的情况。需要优化。 * @author 码歌老薛 * @date 创建时间 猴年马月 * @version 1.0 / public class SingleLazy implements Serializable{ //提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载 private static SingleLazy sh = null; / * 构造器私有 无法创建对象 / private SingleLazy(){ if(sh!=null){ throw new RuntimeException(); } System.out.println(“构造函数被调用了”); } / * 对外提供get方法获取 该类的实例 * @return * @throws InterruptedException */ public static synchronized SingleLazy getInstance() { if(sh==null){ sh = new SingleLazy(); } return sh; } private Object readResolve()throws ObjectStreamException{ return sh; } } 上海尚学堂java培训 shsxt.com三、用法总结1、懒汉式效率是最低的。2、占用资源少 不需要延时加载 枚举优于 饿汉式3、占用资源比较多 需要延时加载 静态内部类 优于 懒汉式更多Java技术文章欢迎阅读上海尚学堂Java培训,免费试学和线上公开课培训课程等你学习。 ...

January 23, 2019 · 2 min · jiezi

每天一个设计模式之装饰者模式

作者按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript和python两种语言实现。诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :)原文地址是:《每天一个设计模式之装饰者模式》欢迎关注个人技术博客:godbmw.com。每周 1 篇原创技术分享!开源教程(webpack、设计模式)、面试刷题(偏前端)、知识整理(每周零碎),欢迎长期关注!如果您也想进行知识整理 + 搭建功能完善/设计简约/快速启动的个人博客,请直接戳theme-bmw0. 项目地址装饰者模式·代码《每天一个设计模式》地址1. 什么是“装饰者模式”?装饰者模式:在不改变对象自身的基础上,动态地添加功能代码。根据描述,装饰者显然比继承等方式更灵活,而且不污染原来的代码,代码逻辑松耦合。2. 应用场景装饰者模式由于松耦合,多用于一开始不确定对象的功能、或者对象功能经常变动的时候。尤其是在参数检查、参数拦截等场景。3. 代码实现3.1 ES6 实现ES6的装饰器语法规范只是在“提案阶段”,而且不能装饰普通函数或者箭头函数。下面的代码,addDecorator可以为指定函数增加装饰器。其中,装饰器的触发可以在函数运行之前,也可以在函数运行之后。注意:装饰器需要保存函数的运行结果,并且返回。const addDecorator = (fn, before, after) => { let isFn = fn => typeof fn === “function”; if (!isFn(fn)) { return () => {}; } return (…args) => { let result; // 按照顺序执行“装饰函数” isFn(before) && before(…args); // 保存返回函数结果 isFn(fn) && (result = fn(…args)); isFn(after) && after(…args); // 最后返回结果 return result; };};/以下是测试代码/const beforeHello = (…args) => { console.log(Before Hello, args are ${args});};const hello = (name = “user”) => { console.log(Hello, ${name}); return name;};const afterHello = (…args) => { console.log(After Hello, args are ${args});};const wrappedHello = addDecorator(hello, beforeHello, afterHello);let result = wrappedHello(“godbmw.com”);console.log(result);3.2 Python3 实现python直接提供装饰器的语法支持。用法如下:# 不带参数def log_without_args(func): def inner(*args, **kw): print(“args are %s, %s” % (args, kw)) return func(*args, **kw) return inner# 带参数def log_with_args(text): def decorator(func): def wrapper(*args, **kw): print(“decorator’s arg is %s” % text) print(“args are %s, %s” % (args, kw)) return func(*args, **kw) return wrapper return decorator@log_without_argsdef now1(): print(‘call function now without args’)@log_with_args(’execute’)def now2(): print(‘call function now2 with args’)if name == ‘main’: now1() now2()其实python中的装饰器的实现,也是通过“闭包”实现的。以上述代码中的now1函数为例,装饰器与下列语法等价:# ….def now1(): print(‘call function now without args’)# … now_without_args = log_without_args(now1) # 返回被装饰后的 now1 函数now_without_args() # 输出与前面代码相同4. 参考JavaScript Decorators: What They Are and When to Use Them《阮一峰ES6-Decorator》《廖雪峰python-Decorator》 ...

January 23, 2019 · 1 min · jiezi

企业应用架构模式-30天阅读计划

构建计算机系统并非易事。随着系统复杂性的增大,构建相应软件的难度将呈指数增大。同其他行业一样,我们只有在不断的学习中进步,从成功经验中学习,从失败教训中学习,才有望克服这些困难。这本书的内容就是这样一些“学习”经验。只有通过模式的总结和学习,才能更有效地与他人进行交流。—选自《企业应用架构模式》0.1 架构软件业的人乐于做这样的事——找一些词汇,并把它们引申到大量微妙而又互相矛盾的含义。一个最大的受害者就是“架构”(architecture)这个词。我个人对“架构”的感觉是,它是一个让人印象深刻的词,主要用来表示一些非常重要的东西。当然,我也会小心,不让这些对“系统结构”的“不恭之词”,影响到读者对本书的兴趣。很多人都试图给“架构”下定义,而这些定义本身却很难统一。能够统一的内容有两点:一点是“最高层次的系统分解”;另一点是“系统中不易改变的决定”。越来越多的人发现:表述一个系统架构的方法不只一种;一个系统中也可能有很多种不同的架构,而且,对于什么在架构上意义重大的看法也会随着系统的生命周期变化。Ralph Johnson经常在邮件列表上发帖,并提出一些令人关注的见解。就在我完成本书初稿的同时,他又发表了一些关于“架构”的观点。他认为,架构是一种主观上的东西,是专家级项目开发人员对系统设计的一些可共享的理解。一般地,这种可共享的理解表现为系统中主要的组成部分以及这些组成间的交互关系。它还包括一些决定,开发者们希望这些决定能及早做出,因为在开发者看来它们是难以改变的。架构的主观性也来源于此——如果你发现某些决定并不像你想象的那么难以改变,那么它就不再与架构相关。到了最后,架构自然就浓缩成一些重要的东西,不论这些东西是什么。在本书中,我提出一些自己的理解,涉及企业应用主要组成部分和我希望能尽早做出的决定。在这些架构模式中,我最欣赏的就是“层次”,将在第1章中进行详细介绍。全书实际上就是关于如何将企业应用组织成不同的层次,以及这些层次之间如何协同工作。大多数重要的企业应用都是按照某种形式的层次分层设计的;当然,在某些情况下,别的设计方式(如管道方式、过滤器方式等)也有它们自己的价值。在本书中我们将不会讨论这些方式,而把注意力集中在层次方式上,因为它是应用最广的设计方式。本书中的一些模式毫无疑问是关于架构的,它们表示了企业应用各主要组成部分间的重要决定;另外一些模式是关于设计的,有助于架构的实现。我没有刻意区分这两类模式,因为正如我们前面讨论的,是否与架构相关往往带有主观性。0.2 企业应用编写计算机软件的人很多,我们通常把这些活动都称为软件开发。但是软件的种类是不同的,每种软件都有自身的挑战性和复杂性。我是在与几个从事电信软件开发的朋友交谈后,意识到这个问题的。企业应用在某些方面要比电信软件简单得多——多线程问题没有那么困难,无需关注硬件设备与软件的集成。但是,在某些方面,企业应用又比电信软件复杂得多——企业应用一般都涉及到大量复杂数据,而且必须处理很多“不合逻辑”的业务规则。虽然有些模式是适合所有软件的,但是大多数模式都还只适合某些特定的领域和分支。我的工作主要是关于企业应用的,因此,这里所谈及的模式也都是关于企业应用的。(企业应用还有一些其他的说法,如“信息系统”或更早期的“数据处理”。)那么,这里的“企业应用”具体指的是什么呢?我无法给出一个精确的定义,但是我可以罗列一些个人的理解。先举几个例子。企业应用包括工资单、患者记录、发货跟踪、成本分析、信誉评估、保险、供应链、记账、客户服务以及外币交易等。企业应用不包括车辆加油、文字处理、电梯控制、化工厂控制器、电话交换机、操作系统、编译器以及电子游戏等。企业应用一般都涉及到持久化数据。数据必须持久化是因为程序的多次运行都需要用到它们——实际上,有些数据需要持久化若干年。在此期间,操作这些数据的程序往往会有很多变化。这些数据的生命周期往往比最初生成它们的那些硬件、操作系统和编译器还要长。在此期间,数据本身的结构一般也会被扩展,使得它在不影响已有信息的基础上,还能表示更多新信息。即使是有根本性的变化发生,或公司安装了一套全新的软件,这些数据也必须被“迁移”到这些全新的应用上。企业应用一般都涉及到大量数据——一个中等规模的系统往往都包含1GB以上的数据,这些数据是以百万条记录的方式存在的。巨大的数据量导致数据的管理成为系统的主要工作。早期的系统使用的是索引文件系统,如IBM的VSAM和ISAM。现代的系统往往采用数据库,绝大多数是关系型数据库。数据库的设计和演化已使其本身成为新的技术领域。企业应用一般还涉及到很多人同时访问数据。对于很多系统来说,人数可能在100人以下,但是对于一些基于Web的系统,人数会呈指数级增长。要确保这些人都能够正确地访问数据,就一定会存在这样或那样的问题。即使人数没有那么多,要确保两个人在同时操作同一数据项时不出现错误,也是存在问题的。事务管理工具可以处理这个问题,但是它通常无法做到对应用开发者透明。企业应用还涉及到大量操作数据的用户界面屏幕。有几百个用户界面是不足为奇的。用户使用频率的差异很大,他们也经常没什么技术背景。因此,为了不同的使用目的,数据需要很多种表现形式。系统一般都有很多批处理过程,当专注于强调用户交互的用例时,这些批处理过程很容易被忽视。企业应用很少独立存在,通常需要与散布在企业周围的其他企业应用集成。这些各式各样的系统是在不同时期,采用不同技术构建的,甚至连协作机制都不同:COBOL数据文件、CORBA系统或是消息系统。企业经常希望能用一种统一的通信技术来集成所有系统。当然,每次这样的集成工作几乎都很难真正实现,所有留下来的就是一个个风格各异的集成环境。当商业用户需要同其业务伙伴进行应用集成时,情况就更糟糕。即使是某个企业统一了集成技术,它们也还是会遇到业务过程中的差异以及数据中概念的不一致性。一个部分可能认为客户是当前签有协议的人;而另外一个部门可能还要将那些以前有合同,但现在已经没有了的人计算在内。再有,一个部门可能只关心产品销售而不关心服务销售。粗看起来,这些问题似乎容易解决,但是,一旦几百个记录中的每个字段都有可能存在着细微差别,问题的规模就会形成不小的挑战——就算唯一知道这些字段之间差别的员工还在公司任职(当然,也许他在你察觉到之前就早已辞职不干了)。这样,数据就必须被不停地读取、合并、然后写成各种不同语法和语义的格式。再接下来的问题是由“业务逻辑”带来的。我认为“业务逻辑”这个词很滑稽,因为很难找出什么东西比“业务逻辑”更加没有逻辑。当我们构建一个操作系统时,总是尽可能地使得系统中的各种事物符合逻辑。而业务逻辑生来就是那样的,没有相当的行政努力,不要想改变它,当然,它们都有自己的理由。你必须面对很多奇怪的条件。而且这些条件相互作用的方式也非常怪异。比如,某个销售人员为了签下其客户几百万美元的一张单,可能会在商务谈判中与对方达成协议,将该项目的年度到账时间推迟两天,因为这样才能与该客户的账务周期相吻合。成千上万的这类“一次特殊情况”最终导致了复杂的业务“无逻辑”,使得商业软件开发那么困难。在这种情况下,必须尽量将这些业务逻辑组织成有效的方式,因为我们可以确定的是,这些“逻辑”一定会随着时间不断变化。对于一些人来说,“企业应用”这个词指的是大型系统。但是 需要注意的是,并不是所有的企业应用都是大型的,尽管它们可能都为企业提供巨大的价值。很多人认为,由于小型系统的规模不大,可以不用太注意它们,而且在某种程度上,这种观点能够带来一定的成本节约。如果一个小型系统失败了,相对于大型系统的失败,这种失败就不会显得那么起眼了。但是,我认为这种思想没有对小型项目的累积作用给予足够的重视。试想,如果在小型项目上能够进行某些改善措施,那么一旦这些改善措施被成功运用于大型项目,它带来的效果就会非常大。实际上,最好是通过简化架构和过程,将一个大型项目简化成小型项目。0.3 企业应用的种类在我们讨论如何设计企业应用以及使用哪些模式之前,明确这样一个观点是非常重要的,即企业应用是多种多样的,不同的问题将导致不同的处理方法。如果有人说“总是这样做” 的时候,就应该敲响警钟了。我认为,设计中最具挑战性(也是我最感兴趣)的地方就是了解有哪些候选的设计方法以及各种不同设计方法之间的优劣比较。进行选择的控件很大,但我在这里只选三个方面。考虑一个B2C(Business to Customer)的网上零售商:人们通过浏览器浏览,通过购物车购买商品。通过购物车购买商品。这样一个系统必须能够应付大量的客户,因此,其解决方案不但要考虑到资源利用的有效性,还要考虑到系统的可伸缩性,以便在用户规模增大时能够通过增加硬件的办法加以解决。该系统的业务逻辑可以非常简单:获取订单,进行简单的价格计算和发货计算,给出发货信息。我们希望任何人都能够访问该系统,因此用户界面可以选用通用的Web表现方式,以支持各种不同的浏览器。数据源包括用来存放订单的数据库,还可能包括某种与库存系统的通信交流,以便获得商品的可用性信息和发货信息。再考虑一个租约合同自动处理系统。在某些方面,这样的系统比起前面介绍的B2C系统要简单,因为它的用户数很少(在特定时间内不会超过100个),但是它的业务逻辑却比较复杂。计算每个租约的月供,处理如提早解约和延迟付款这样的事件,签订合同时验证各种数据,这些都是非常复杂的任务,因为租约领域的许多竞争都是以过去的交易为基础稍加变化而出现的。正是因为规则的随意性很大,才使得像这样一个复杂领域具有挑战性。这样的系统在用户界面(UI)上也很复杂。这就要求HTML界面要能提供更丰富的功能和更复杂的屏幕,而这些要求往往是HTML界面目前无法达到的,需要更常规的胖客户界面。用户交互的复杂性还会带来事务行为的复杂性:签订租约可能要耗时12个小时,这期间用户要处于一个逻辑事务中。一个复杂的数据库设计方案中可能也会涉及到200多个表以及一些有关资产评估和计价的软件包。第三个例子是一家小型公司使用的简单的“开支跟踪系统”。这个系统的用户很少,功能简单,通过HTML表现方式可以很容易实现,涉及的数据源表项也不多。尽管如此,开发这样的系统也不是没有挑战。一方面你必须快速地开发出它,另一方面你又必须为它以后可能的发展考虑;也许以后会为它增加赔偿校验的功能,也许它会被集成到工资系统中,也许还要增加关于税务的功能,也许要为公司的CFO生成汇总报表,也许会被集成到一个航空订票Web Service中,等等。如果在这个系统的开发中,也试图使用前面两个例子中的一些架构,可能会影响开发进度。如果一个系统会带来业务效益(如所有的企业应用应该的那样),则系统进度延误同样也是开销。如果现在不做决策又有可能影响系统未来的发展。但是,如果现在就考虑了这些灵活性但是考虑不得当,额外的复杂性又可能会影响到系统的发展,进一步延误系统部署,减少系统的效益。虽然这类系统很小,但是一个企业中往往有很多这样的系统,这些系统的架构不良性累积起来,后果将会非常可怕。这三个企业应用的例子都有难点,而且难点各不相同。当然,也不可能有一个适合于三者的通用架构。选择架构时,必须很清楚地了解面临的问题,在理解的基础上再来选择合适的设计。本书中也没有一个通用的解决方案。实际上,很多模式仅仅是一些可选方案罢了。即使你选择了某种模式,也需要进一步根据面临的问题来修改模式。在构建企业应用时,你不思考是不行的。所有书本知识只是给你提供信息,作为你做决定的基础。模式是这样,工具也同样如此。在系统开发时应该选取尽可能少的工具,同时也要注意,不同的工具擅长处理的方面也不同,切记不要用错了工具,否则只会事倍功半。0.4 关于性能的考虑很多架构的设计决策和性能有关。对于大多数与性能相关的问题,我的办法是首先建立系统,调试运行,然后通过基于测量的严格的优化过程来提高性能。但是,有一些架构上的决策对性能的影响,可能是后期优化难以弥补的。而且即使这种影响可以在后期很容易地弥补,参与这个项目的人们任然会从一开始就担心这些决策。在这样的一本书中讨论性能通常很困难。这是因为“眼见为实”:所有那些关于性能的条条框框,不在你的具体系统中配置运行一下,是很难有说服力的。我也经常看到一些设计方案因为性能方面的考虑而被接受或拒绝,但是一旦有人在真实的设置环境中做一些测量,就会证明这些考虑是错误的。本书将提出一些这方面的建议,包括尽量减少远程调用(它在很长时间内都被认为是优化性能的好建议)。尽管如此,还是建议读者在运用这些原则之前,在你的应用中具体试一试。同样,本书中的样例代码也有一些地方为了提高可读性而牺牲了效率。在你的系统中,需要自行决定是否进行优化。在做性能优化后,一定要与优化前进行测量对比,以确定真的得到了优化,否则,你可能只是破坏了代码的可读性。还有一个很重要的推论:配置上的重大变化会使得某些性能优化失效。因此,在升级虚拟机、硬件、数据库或其他东西到新的版本时,必须重新确认性能优化工作的有效性。很多情况下,配置变更都会对性能优化有影响,有时候你真的会发现,以前为了提升性能做的优化,在新环境下居然影响性能。关于性能的另一个问题是很多术语的使用不一致。最明显的例子就是“可伸缩性”(scalability),它可能有6-7种含义。下面我使用其中一些术语。响应时间是系统完成一次外部请求处理所需要的时间。这些外部请求可能是用户交互行为,例如按下一个按钮,或是服务器API调用。响应性不同于请求处理,它是系统响应请求的速度有多快。这个指标在许多系统里非常重要,因为对于一些系统而言,如果其响应性太慢,用户将难以忍受——尽管其响应时间可能不慢。如果在请求处理期间,系统一直处于等待状态,则系统的响应性和响应时间是相同的。然而,如果能够在处理真正完成之前就给用户一些信息表明系统已经接到请求,则响应性就会好一些。例如,在文件拷贝过程中,为用户提供一个“进度条”,将会提高用户界面的响应性,但并不会提高响应时间。等待时间是获得系统任何形式响应的最小时间,即使应该做的工作并不存在。通常它是远程系统中的大问题。假设我们让程序什么都不做,只是调用返回即可,则如果在本机上运行程序,一般都会立即得到响应。但是,如果在远程计算机上运行程序,情况就不一样,往往需要数秒的时间才能得到响应。因为从发出请求到得到响应的数秒时间主要用于排除使信息在线路上传输的困难。作为应用开发者,我经常对等待时间无能为力。这也是为什么要尽量避免远程调用的原因。吞吐率是给定时间内能够处理多大的请求量。如果考察的是文件拷贝,则吞吐率可以用每秒字节量来表示。对于企业应用来说,吞吐率通常用每秒事务数(tps)来度量。这种方法的一个问题是指标依赖于事务的复杂程度。对于特定系统的测试,应该选取普通的事务集合。在这里,性能或指吞吐率,或者指响应时间,由用户自己决定。当通过某种优化技术后,使得系统的吞吐率提高了,但是响应时间下降了,这时就不好说系统的性能提高了,最好用更准确的术语表示。从用户角度而言,响应性往往比响应时间更重要,因此,为了提高响应性而损失一些响应时间或者吞吐率是值得的。负载是关于系统当前负荷的表述,也许可以用当前有多少用户与系统相连来表示。负载有时也作为其他指标(如响应时间)的背景。因此,我们可以说:在10个用户的情况下,请求响应时间是0.5秒,在20个用户的情况下,请求响应时间是2秒。负载敏感度是指响应时间随负载变化的程度。假设:系统A在1020个用户的情况下,请求响应时间都是0.5秒;系统B在10个用户的情况下,请求响应时间是0.2秒,在20个用户的情况下,请求响应时间上升到2秒。此时,系统A的负载敏感度比系统B低;我们还可以使用术语衰减(degradation),称系统B衰减得比系统A快。效率是性能除以资源。如果一个双CPU系统的性能是30tps,另一个系统有4个同样的CPU,性能是40tps,则前者效率高于后者。系统的容量是指最大有效负载或吞吐率的指标。它可以是一个绝对最大值或性能衰减至低于一个可接受的阈值之前的临界点。可伸缩性度量的是向系统中增加资源(通常是硬件)对系统性能的影响。一个可伸缩性的系统允许在增加了硬件后,能够有性能上的合理提高。例如,为了使吞吐率提高一倍,要增加多少服务器等。垂直可伸缩性或称垂直延展,通常指提高单个服务器的性能,例如增加内存。水平可伸缩性或称水平延展,通常指增加服务器的数目。问题是,设计决策对所有性能指标的作用并不相同。比如,某个服务器上运行着两个软件系统:Swordfish的容量是20tps,而Camel的容量是40tps。哪一个的性能更高?哪一个的可伸缩性好?仅凭这些数据,我们无法回答关于可伸缩性的问题,我们只能说Camel系统在单片机上的效率更高。假设又增加了一台服务器后,我们发现:Swordfish的容量是35tps,Camel的容量是50tps。尽管Camel的容量仍然大于Swordfish,但是后者在可伸缩性上却显得比前者更好。假设我们继续增加服务器数目后发现:Swordfish每增加一台服务器提高15tps,Camel每增加一台服务器提高10tps。在获得了这些数据后,我们才可以说,Swordfish的水平可伸缩性比Camel好,尽管Camel在5个服务器以下会有更好的效率。当构建企业应用系统时,关注硬件的可伸缩性往往比关注容量或效率更重要。如果需要,可伸缩性可以给予你获得更好性能的选择,可伸缩性也可以更容易实现。有时,设计人员费了九牛二虎之力才提高了少许容量,其开销还不如多买一些硬件。换句话说,假设Camel的费用比Swordfish高,高出的部分正好可以买几台服务器,那么选择Swordfish可能更合算,尽管你目前只需要40tps。现在人们经常抱怨软件对硬件的依赖性越来越大,有时为了运行某些软件就不得不对硬件进行升级,就像我一样,为了用最新版本的Word,就必须不断地升级笔记本电脑。但是总的来说,购买新硬件还是比修改旧软件来得便宜。同样,增加更多的服务器也比增加更多的程序员来得便宜——只要你的系统有足够的可伸缩性。0.5 模式模式的概念早就有了。我在这里不想把这段历史重新演绎一遍。只是想简单谈谈我对模式和它们为什么是描述设计的重要手段的一些看法。模式没有统一的定义。可能最好的起点是Christopher Alexander给出的定义(这也是许多模式狂热者的灵感来源):“每一个模式描述了一个在我们周围不断重复发生的问题以及该问题解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”[Alexander et al.]。尽管Alexander是建筑家,他谈论的是建筑模式,但其定义也能很好地适用于软件业。模式的核心就是特定的解决方案,它有效而且有足够的通用性,能解决重复出现的问题,模式的另一种视角是把它看成一组建议,而创造模式的艺术则是将很多建议分解开来,形成相互独立的组,在此基础上可以相对独立地讨论它们。模式的关键点是它们源于实践。必须观察人们的工作过程,发现其中好的设计,并找出“这些解决方案的核心”。这并不是一个简单的过程,但是一旦发现了某个模式,他将是非常有价值的。对于我来说,价值之一是能够撰写这样一本参考书。你不必通读本书的全部内容,也不必通读所有有关于模式的书。你只需要了解到这些模式都是干什么的,它们解决什么问题,它们是如何解决问题的,就足够了。这样,一旦碰到类似问题,就可以从书中找出相应的模式。那时,再深入了解相应的模式也不迟。一旦需要使用模式,就必须知道如何将它运用于当前的问题。使用模式的关键之一是不能盲目使用,这也是模式工具为什么都那么惨的原因。我认为模式是一种“半生不熟品”,为了用好它,还必须在自己的项目中把剩下的那一半“火候”补上。我本人每次在使用模式时,都会东改一点西改一点。因此你会多次看到同一解决方案,但没有一次是完全相同的。每个模式相对独立,但又不彼此孤立。有时候它们相互影响,如影随形。例如,如果在设计中使用了领域模型,那么经常还会用到类表继承。模式的边界本来也是模糊的,我在本书中也尽量让它们各自独立。如果有人说“使用工作单元”,你就可以直接去看工作单元这个模式如何使用,而不必阅读全书。如果你是一个有经验的企业应用设计师,也许会对大多数模式都很熟悉。希望本书不会给你带来太大的失望。(实际上我在前言里面已经提醒过了。)模式不是什么新鲜概念。因此,撰写模式书籍的作者们也不会声称我们“发明”了某某模式,而是说我们“发现”了某某模式。我们的职责是记录通用的解决方案,找出其核心,并把最终的模式记录下来。对于一个高级设计师,模式的价值并不在于它给予你一些新东西,而在于它能帮助你更好地交流。如果你和你的同事都明白什么是远程外观,你就可以这样非常简洁地交流大量信息:“这个类是一个远程外观模式。”也可以对新人说:“用数据传输对象模式来解决这个问题。”他们就可以查找本书来搞清楚如何做。模式为设计提供了一套词汇,这也是为什么模式的名字这么重要的原因。本书的大多数模式是用来解决企业应用的,基本模式一章(见第18章)则更通用一些。我把它们包含进来的原因是:在前面的讨论中,我引用了这些通用的模式。0.5.1 模式的结构每个作者都必须选择表达模式的形式。一些人采用的表达基于模式的一些经典教材如[Alexander et al.]、[Gang of Four]或[POSA]。另一些人用他们自己的方式。我在这个问题上也斟酌了很久。一方面我不想象GOF一样太精炼,另一方面我还要引用他们的东西。这就形成了本书的模式结构。第一部分是模式的名字。模式名非常重要,因为模式的目的之一就是为设计者们交流提供一组词汇。因此,如果我告诉你Web服务器是用前端控制器和转换试图构建的,而你又了解这些模式,那么你对我的Web服务器的架构就会非常清楚了。接下来的两部分是相关的:意图和概要。意图用一两句话总结模式;概要是模式的一种可视化表示,通常是(但不总是)一个UML图。这主要是想给模式一个简单的概况,以帮助记忆。如果你对模式已经“心知肚明”,只是不知道它的名字,那么模式的意图和概要这两部分就能为你提供足够的信息。接下来的部分描述了模式的动机。这可能不是该模式所能解决的唯一问题,但却是我认为最具代表性的问题。“运行机制”部分描述了解决方案。在这一部分,我会讨论一些实现问题以及我遇到的变化情况。我会尽可能独立于平台来讨论——也有一个部分是针对平台来讨论的,如果不感兴趣可以跳过这部分。为了便于解释,我用了一些UML图来辅助说明。“使用动机”部分描述了模式何时被使用。这部分讨论是使我选择该模式而不是其他模式的权衡考虑。本书中很多模式都可以相互替代,例如页面控制器和前端控制器可以相互替代。很少有什么模式是非它不可的。因此,每当我选择了一种模式之后,我总是问自己“你什么时候不用它?”这个问题也经常驱使我选择其他方案。“进一步阅读”部分给出了与该模式相关的其他读物。它并不完善。我只选择我认为有助于理解模式的参考文献,所以我去掉了对本书内容没有价值的任何讨论,当然其中也可能会遗漏一些我不知道的模式。我也没有提到一些我认为可能读者无法找到的参考文献,再就是一些不太稳定的Web链接。我喜欢为模式增加一个或几个例子。每个例子都非常简单,它们是用Java语言或C#语言编写的。我之所以选择两种语言,是因为它们可能是目前绝大多数专业程序员都能读懂的语言。必须注意,例子本身不是模式。当你使用模式时,不要想当然地认为它会和例子一样,也不要把例子看成某种形式的宏替换。我把例子编得尽量简单以突出其中模式相关的部分。当然,省略的部分并不是不重要,只是它们一般都特定于具体环境,这也是为什么模式在使用时一般都必须做适当调整的原因。为了尽量使例子简单但是又能够突出核心意思,我主要选择那些简单而又明确的例子,而不是那些来自于系统中的复杂例子。当然,在简单和过分之间掌握平衡是不容易的,但是我们必须记住:过分强调具体应用环境反而会增加模式的复杂性,使得模式的核心内容不易理解。这就是为什么我在选择例子时选取的是一些相互独立的例子而不是相互关联的例子的原因。独立的例子有助于对模式的理解。但是在如何将这些模式联合在一起使用上却支持不多。相互关联的例子则相反,它体现了模式间是如何相互作用的,但是对其中每个模式的理解却依赖于对其他所有模式的理解。理论上,是可以构造出既相互关联又相互独立的例子,但这是一项非常艰巨的工作——至少对于我来说是这样。因此,我选择了相互独立的例子。例子中的代码本身也主要用来增强对思想的理解。因此,在其他一些方面考虑可能不够——特别是错误处理,在这方面,我没有花费很多笔墨,因为到目前为止,我还没有得出错误处理方面的模式。在此,那些代码纯粹用来说明模式,而并不是用来显示如何对任何特定的业务问题进行建模。正是由于这些原因,我没有把这些代码放到我的网站上供大家下载。为了让那些基本的思想在应用设置下有所意义,本书的每个样例代码都充满着太多的“脚手架”来简化它们。并不是每个模式中都包含上面所述的各个部分。如果我不能想出很好的例子或动机等内容,我就会把相应部分省略。0.5.2 模式的局限性正如我在前言中所述,对于企业应用开发而言,本书介绍的模式并不全面。我对本书的要求,不在于它是否全面,而在于它是否有用。模式这个领域太大了,单凭一个人的头脑是无法做到面面俱到的,更不用说是一本书了。本书中所列的模式都是我在具体领域中遇到的,但这并不表明我已经理解了每一个模式以及它们之间的关系。本书的内容只是反映了我在写书时的理解,在编写本书的过程中,我对相关内容的理解也不断发展和加深,当然,在本书发表之后,我仍然希望本人对模式的理解还能够继续发展。对于软件开发而言,有一点是可以肯定的,那是软件开发永远不会停止。当你使用模式时请记住:它们只是开始,而不是结束。任何作者去囊括项目开发中的所有变化和技术是不可能的。我编写本书的目的也只是作为一个开始,希望它能够把我自己的和我所了解的经验和教训传递给读者,你们可以在此基础上继续努力。请大家记住:所有模式都是不完备的,你们都有责任在自己的系统中完善它们,你们也会在这个过程中得到乐趣。——选自:《企业应用架构模式》 [Patterns of Enterprise Application Architecture] [英] 福勒 著;王怀民,周斌 译

January 23, 2019 · 1 min · jiezi

React组件设计规则

react的目的是将前端页面组件化,用状态机的思维模式去控制组件。组件和组件之间肯定是有关系得,通过合理得组件设计,给每一个组件划定合适得边界,可以有效降低当我们对页面进行重构时对其他组件之间得影响。同时也可以使我们得代码更加美观。1、高耦合低内聚。高耦合:将功能联系紧密得部分放到一个容器组件内对外暴漏出index.js,目录结构如下:├── components│ └── App└── index.js低内聚:当这个组件在调用页面直接删除时,不会触发任何影响;减少无必要的重复渲染;减小重复渲染时影响得范围。2、展示组件和容器组件展示组件容器组件关注事物的展示关注事物如何工作可能包含展示和容器组件,并且一般会有DOM标签和css样式可能包含展示和容器组件,并且不会有DOM标签和css样式常常允许通过this.props.children传递提供数据和行为给容器组件或者展示组件对第三方没有任何依赖,比如store 或者 flux action调用flux action 并且提供他们的回调给展示组件不要指定数据如何加载和变化作为数据源,通常采用较高阶的组件,而不是自己写,比如React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()很少有自己的状态,即使有,也是自己的UI状态这里重点说下this.props.children。通过this.props.children我们很容易让我们得组件变的低内聚。在实际开发中往往会遇到用纯组件写得展示组件下还有要继续跟跟数据打交道得容器组件。这里就用this.props.children套上这些容器组件就可以了。然后被套得容器组件可以继续按照上面得规则新建个文件夹暴漏出index.js这种写法。这种写法得最大好处是你很快就能找到你写得这个组件是在哪,是干嘛得,影响了哪。3、从顶部向下得单向数据流当我们得设计满足上面这些条件时,使用从顶部向下的单向数据流会让我们在使用一些类似于redux这种得状态管理工具时,影响的范围更加可控,再通过shouldComponentUpdate来减少不必要的渲染。(不过这么写确实挺麻烦的,但是react从 v16.3开始使用新的生命周期函数getDerivedStateFromProps来强制开发者对这一步进行优化)4、受控组件和非受控组件有许多的web组件可以被用户的交互发生改变,比如:<input>,<select>。这些组件可以通过输入一些内容或者设置元素的value属性来改变组件的值。但是,因为React是单向数据流绑定的,这些组件可能会变得失控: 1.一个维护它自己state里的value值的<Input>组件无法从外部被修改2.一个通过props来设置value值的<Input>组件只能通过外部控制来更新。受控组件:一个受控的<input>应该有一个value属性。渲染一个受控的组件会展示出value属性的值。 一个受控的组件不会维护它自己内部的状态,组件的渲染单纯的依赖于props。也就是说,如果我们有一个通过props来设置value的<input>组件,不管你如何输入,它都只会显示props.value。换句话说,你的组件是只读的。在处理一个受控组件的时候,应该始终传一个value属性进去,并且注册一个onChange的回调函数让组件变得可变。非受控组件:一个没有value属性的<input>就是一个非受控组件。通过渲染的元素,任意的用户输入都会被立即反映出来。非受控的<input>只能通过OnChange函数来向上层通知自己被用户输入的更改。 #### 混合组件:同时维护props.value和state.value的值。props.value在展示上拥有更高的优先级,state.value代表着组件真正的值。5、使用高阶组件(HOC)简单定义:一个接收react组件作为参数返回另外一个组件的函数。可以做什么:代码复用,代码模块化增删改props使用案例:比方说公司突然要给前端代码不同的点击埋点,就可以使用hoc包一层,再不改动原来各处代码得同时进行了合理得改动。6、增删改查标准流程增:填写数据,验证数据,插入数据,重新查询数据列表。删:确认删除,重新查询数据列表。查:查询数据列表,分页显示改:填写数据,验证数据,修改数据,重新查询数据列表其实设计组件时没必要过早的组件化。我们可以先快速的写出一个版本,然后再根据实际设计拆分以应对项目初期的需求快速变更。然后一点一点的按照设计模式去改变我们的项目,只要设计模式合理拆分其实是一个很流畅和自然的事情。推荐文章:1、React技术栈进阶之路之设计模式篇2、React组件设计3、react如何通过shouldComponentUpdate来减少重复渲染4、reactJS 干货(reactjs 史上最详细的解析干货)

January 22, 2019 · 1 min · jiezi

创建型模式:抽象工厂

个人博客原文:创建型模式:抽象工厂五大创建型模式之三:抽象工厂。简介姓名 :抽象工厂英文名 :Abstract Factory Pattern价值观 :不管你有多少产品,给我就是了个人介绍 :Provide an interface for creating families of related or dependent objects without specifying their concrete classes.为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。(来自《设计模式之禅》)今天讲的是抽象工厂模式,小伙伴可能有疑问,抽象工厂和工厂方法之间都有工厂,那肯定是有什么联系的,具体是什么关系呢?简单的说:工厂方法是在解决一个产品多个层级方面的事情;而抽象工厂致力于解决多个产品多个层级方面的事情。举个例子:汽车是由很多零件组成的,比如引擎、轮胎、方向盘等等。现在如果我们是轮胎生产方,要生产宝马轮胎和奔驰轮胎,要用工厂方法还是抽象工厂实现呢?答案是:工厂方法。轮胎是一个产品,宝马轮胎和奔驰轮胎是 2 个不同层级的轮胎,所以用工厂方法解决就足够。假如现在我们是汽车生产方,要生产宝马汽车和奔驰汽车,汽车又包含轮胎和方向盘等等,要用哪个来实现?既然是上面的是工厂方法,那这个就用抽象工厂,因为这涉及到多个产品(轮胎、方向盘等等)和 2 个层级(宝马和奔驰)。这里还没有讲抽象工厂的概念就说了工厂方法和抽象方法的区别,是不是有点陌生?嗯,先记住这个概念,分清楚两者的区别。在不同场景使用不同的设计模式。上面定义中:为创建一组相关或相互依赖的对象提供一个接口。这样子理解这句话,比如上面说的轮胎和方向盘,宝马汽车用的轮胎和方向盘需要都是宝马品牌的,也就是说在安装宝马汽车的轮胎和方向盘的时候,得用宝马生产的轮胎和方向盘,重要的一点是:轮胎和方向盘是互相依赖的,不能在宝马汽车上安装奔驰轮胎和宝马方向盘,因为有这个依赖关系,所以我们需要提供一个额外的接口,来保证宝马汽车使用的轮胎和方向盘都是宝马生产的。这就是抽象工厂干的事情。你要的故事上面用汽车安装轮胎和方向盘的例子,那这里为了让大家能深入理解,就不用其他例子了。在一个设计模式讲解的过程中,我觉得用一个案例来讲解可以减少读者的阅读理解成本,为了写设计模式这一系列文章,看了不少设计模式方面的书籍,有些书籍在讲解一个设计模式的时候,用了不止一个例子,读完之后印象不是很深刻。这个系列写完之后,想要的效果是:不需要记住设计模式的定义,把这些故事以及故事对应是讲哪个设计模式都记住了,就真正掌握了这些内容了。public class AbstractFactoryTest { public static void main(String[] args) { // 宝马员工安装轮胎和方向盘 AbstractCarFactory bmwCarFacatory = new BMWCarFactory(); bmwCarFacatory.installWheel(); bmwCarFacatory.installSteeringWheel(); // 奔驰员工安装轮胎和方向盘 AbstractCarFactory mercedesCarFacatory = new MercedesCarFacatory(); mercedesCarFacatory.installWheel(); mercedesCarFacatory.installSteeringWheel(); }}/** * 汽车抽象工厂 /interface AbstractCarFactory { void installWheel(); void installSteeringWheel();}/* * 宝马工厂 /class BMWCarFactory implements AbstractCarFactory { @Override public void installWheel() { WheelFacatory wheelFacatory = new BMWWheelFacatory(); String wheel = wheelFacatory.createWheel(); System.out.println(“安装轮胎:” + wheel); } @Override public void installSteeringWheel() { SteeringWheelFacatory steeringWheelFacatory = new BMWSteeringWheelFacatory(); String steeringWheel = steeringWheelFacatory.createSteeringWheel(); System.out.println(“安装方向盘:” + steeringWheel); }}/* * 奔驰工厂 /class MercedesCarFacatory implements AbstractCarFactory { @Override public void installWheel() { WheelFacatory wheelFacatory = new MercedesWheelFacatory(); String wheel = wheelFacatory.createWheel(); System.out.println(“安装轮胎:” + wheel); } @Override public void installSteeringWheel() { SteeringWheelFacatory steeringWheelFacatory = new MercedesSteeringWheelFacatory(); String steeringWheel = steeringWheelFacatory.createSteeringWheel(); System.out.println(“安装方向盘:” + steeringWheel); }}/* * 轮胎工厂 /interface WheelFacatory { String createWheel();}/* * 宝马轮胎工厂 /class BMWWheelFacatory implements WheelFacatory { @Override public String createWheel() { System.out.println(“宝马轮胎工厂生产轮胎”); return “宝马轮胎”; }}/* * 奔驰轮胎工厂 /class MercedesWheelFacatory implements WheelFacatory { @Override public String createWheel() { System.out.println(“奔驰轮胎工厂生产轮胎”); return “奔驰轮胎”; }}/* * 方向盘工厂 /interface SteeringWheelFacatory { String createSteeringWheel();}/* * 宝马方向盘工厂 /class BMWSteeringWheelFacatory implements SteeringWheelFacatory { @Override public String createSteeringWheel() { System.out.println(“宝马方向盘工厂生产方向盘”); return “宝马方向盘”; }}/* * 奔驰方向盘工厂 */class MercedesSteeringWheelFacatory implements SteeringWheelFacatory { @Override public String createSteeringWheel() { System.out.println(“奔驰方向盘工厂生产方向盘”); return “奔驰方向盘”; }}代码:AbstractFactoryTest.java还是和以往一样,思维开拓一下,这里列举的是给汽车安装轮胎和方向盘,汽车不止这些,如果要加个安装引擎呢?要怎么实现?这里我就不写出来了,让小伙伴尝试一下,写出来了就理解抽象模式这个设计模式啦。总结简单工厂、工厂方法、抽象工厂这几个工厂相关的设计模式的基本内容都讲完了,这几个模式都是为了解耦,为了可扩展。这里要着重说一下,三者之间没有好坏之分,只有在具体的场景才能发挥它们各自的优势。在单产品多层级,层级数量不多的情况下,可以使用简单工厂,层级多且需要支持扩展,可以使用工厂方法;在多产品多层级,可以使用抽象工厂。参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》推荐阅读:创建型模式:单例模式创建型模式:工厂方法希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。公众号之设计模式系列文章 ...

January 20, 2019 · 2 min · jiezi

我设计一个phpms框架前的准备

phpms框架源码https://github.com/wuxiumu/ms一、PHP常用的四种数据结构简介:spl是php的一个标准库。官方文档:http://php.net/manual/zh/book…<?php //spl(php标准库)数据结构 /** * 栈(先进后出) /function zan(){ $stack = new SplStack(); $stack->push(‘data1’);//入栈(先进后出) $stack->push(‘data2’);//入栈 $stack->push(‘data3’);//入栈 echo $stack->pop().PHP_EOL;//出栈 echo $stack->pop().PHP_EOL;//出栈 echo $stack->pop().PHP_EOL;//出栈} /* 队列(先进先出) /function duilie(){ $queue = new SplQueue(); $queue->enqueue(‘data4’);//入队列 $queue->enqueue(‘data5’);//入队列 $queue->enqueue(‘data6’);//入队列 echo $queue->dequeue().PHP_EOL;//出队列 echo $queue->dequeue().PHP_EOL;//出队列 echo $queue->dequeue().PHP_EOL;//出队列} / * 堆 /function dui(){ $heap = new SplMinHeap(); $heap->insert(‘data8’);//入堆 $heap->insert(‘data9’);//入堆 $heap->insert(‘data10’);//入堆 echo $heap->extract().PHP_EOL;//从堆中提取数据 echo $heap->extract().PHP_EOL;//从堆中提取数据 echo $heap->extract().PHP_EOL;//从堆中提取数据} /* * 固定数组(不论使不使用,都会分配相应的内存空间) /$array = new SplFixedArray(15);$array[‘0’] = 54;$array[‘6’] = 69;$array[‘10’] = 32;var_dump($array);二、PHP链式操作的实现(原理)1、入口文件index.phpheader(“content-type:text/html;charset=utf-8”); define(‘PHPMSFRAME’,DIR); define(‘CORE’,PHPMSFRAME.’/core’);define(‘APP’,PHPMSFRAME.’/app’);define(‘MODULE’,‘app’);define(‘DEBUG’,true); include “vendor/autoload.php”;if(DEBUG){ $whoops = new \Whoops\Run; $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); $whoops->register(); ini_set(‘display_error’, ‘On’);}else{ ini_set(‘display_error’, ‘Off’);}include CORE.’/common/function.php’;include CORE.’/phpmsframe.php’;spl_autoload_register(’\core\phpmsframe::load’);\core\phpmsframe::run();2、自动加载类corephpmsframe.php<?phpnamespace core;class phpmsframe { public static $classMap = array(); public $assign; static public function run() { $route = new \core\lib\route(); $ctrlClass = $route->ctrl; $action = $route->action; $ctrlfile = APP.’/ctrl/’.$ctrlClass.‘Ctrl.php’; $ctrlClass = ‘\’.MODULE.’\ctrl\’.$ctrlClass.‘Ctrl’; if(is_file($ctrlfile)){ include $ctrlfile; $ctrl = new $ctrlClass(); $ctrl->$action(); }else{ $msg = “控制器 $ctrlClass 不存在\n”; self::reportingDog($msg); } } static public function load($class) { if(isset($classMap[$class])){ return true; }else{ $class = str_replace(’\’, ‘/’, $class); $file = PHPMSFRAME.’/’.$class.’.php’; if(is_file($file)){ include $file; self::$classMap[$class] = $class; }else{ return false; } } } public function assign($name,$value){ $this->assign[$name]=$value; } public function display($file){ $file_path = APP.’/views/’.$file; if(is_file($file_path)){ /twig模板/ $loader = new \Twig_Loader_Filesystem(APP.’/views’); $twig = new \Twig_Environment($loader, array( ‘cache’ => PHPMSFRAME.’/cache’, ‘debug’=>DEBUG, )); $template = $twig->load($file); $template->display($this->assign?$this->assign:’’); /twig模板end/ /原生模板/ //extract($this->assign); //include $file_path; /原生模板end/ } } static private function reportingDog($msg){ echo $msg."\n"; include ‘smile/havefun.php’; $num = str_pad(rand(00,32),2,“0”,STR_PAD_LEFT); $num = “str_”.$num; $Parsedown = new \Parsedown(); echo $Parsedown->text($$num); $num = “str_".rand(50,84); echo $Parsedown->text($$num); exit; }}3、数据库类注:只是原理,并没有对方法进行具体的封装,具体的封装还是看个人喜好去定链式查询的风格。corelibmodel.php<?phpnamespace core\lib;use core\lib\drive\database\medooModel;class model extends medooModel{ }corelibdrivedatabasemedooModel.php<?php/* * 继承medoo第3方类库,模型基类 /namespace core\lib\drive\database;use core\lib\conf;use Medoo\Medoo;class medooModel extends Medoo{ //初始化,继承pdo应该是就可以直接用手册中的pdo中的方法了 public function __construct() { $option = conf::all(‘database’); parent::__construct($option[‘mysql_medoo_conf’]); }}三、PHP魔术方法的使用在php设计模式中,会涉及到很多魔术方法的使用,这里也对经常会用到的魔术方法进行简单总结。1、框架入口文件corephpmsframe.php /* * 魔术方法的使用 / / 这是一个魔术方法,当一个对象或者类获取其不存在的属性的值时, * 如:$obj = new BaseController ; * $a = $obj -> a ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __get($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类给其不存在的属性赋值时, * 如:$obj = new BaseController ; * $obj -> a = 12 ; * 该方法(_set(属性名,属性值))会被自动调用,这样做很友好,可以避免系统报错 / public function __set($property_name,$value){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类的不存在属性进行isset()时, * 注意:isset 用于检查一个量是否被赋值 如果为NULL会返回false * 如:$obj = new BaseController ; * isset($obj -> a) ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __isset($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 这是一个魔术方法,当一个对象或者类的不存在属性进行unset()时, * 注意:unset 用于释放一个变量所分配的内存空间 * 如:$obj = new BaseController ; * unset($obj -> a) ; * 该方法会被自动调用,这样做很友好,可以避免系统报错 / public function __unset($property_name){ $msg = “属性 $property_name 不存在\n”; self::reportingDog($msg); } / 当对这个类的对象的不存在的实例方法进行“调用”时,会自动调用该方法, * 这个方法有2个参数(必须带有的): * $methodName 表示要调用的不存在的方法名; * $argument 是一个数组,表示要调用该不存在的方法时,所使用的实参数据, / public function __call($methodName,$argument){ $msg = “实例方法 $methodName 不存在\n”; self::reportingDog($msg); }四、三种基础设计模式1、工厂模式通过传入参数的不同,来实例化不同的类。$cache =\Core\extend\CacheFactory::getCacheObj(‘redis’,array( ‘host’ => ‘127.0.0.1’, ‘pass’ => ‘myRedis&&&’ ));var_dump($cache);coreextendCacheFactory.php<?phpnamespace core\extend;class CacheFactory{ const FILE = 1; const MEMCACHE = 2; const REDIS = 3; static $instance;//定义静态属性,用于存储对象 /* * 工厂类创建缓存对象 * @param $type 指定缓存类型 * @param array $options 传入缓存参数 * @return FileCache|Memcache|RedisCache / static function getCacheObj($type, array $options) { switch ($type) { case ‘file’: case self::FILE: self::$instance = new FileCache($options); break; case ‘memcache’: case self::MEMCACHE: self::$instance = new Memcache($options); break; case ‘redis’: case self::REDIS: self::$instance = new RedisCache($options); break; default: self::$instance = new FileCache($options); break; } return self::$instance; }}2、单例模式保证一个类只实例化一个类对象,进而减少系统开销和资源的浪费//单例模式创建对象$obj = Extend\SingleObject::getInstance();$obj2 = Extend\SingleObject::getInstance();var_dump($obj,$obj2);//从结果可以看出,两个实例化的对象其实是一个对象coreextendSingleObject.php<?phpnamespace core\extend;class SingleObject{ //私有的静态属性,用于存储类对象 private static $instance = null; //私有的构造方法,保证不允许在类外 new private function __construct(){ } //私有的克隆方法, 确保不允许通过在类外 clone 来创建新对象 private function __clone(){ } //公有的静态方法,用来实例化唯一当前类对象 public static function getInstance() { if(is_null(self::$instance)){ self::$instance = new self; } return self::$instance; }}3、注册树模式将我们用到的对象注册到注册树上,然后在之后要用到这个对象的时候,直接从注册树上取下来就好。(就和我们用全局变量一样方便)coreextendRegisterTree,php<?phpnamespace core\extend;class RegisterTree{ static protected $objects;//静态类属性,用于储存注册到注册树上的对象 /* * 将对象注册到注册树上 * @param $alias 对象的别名 * @param $object 对象 / static function setObject($alias,$object) { self::$objects[$alias] = $object; } /* * 从注册树上取出给定别名相应的对象 * @param $alias 将对象插入到注册树上时写的别名 * @return mixed 对象 / static protected function getObject($alias) { return self::$objects[$alias]; } /* * 将对象从注册树上删除 * @param $alias 将对象插入到注册树上时写的别名 / public function unsetObject($alias) { unset(self::$objects[$alias]); }}关于注册树模式,这里推荐一篇文章 ,可以方便理解。 https://segmentfault.com/a/11…五、其他常见的8种PHP设计模式1、适配器模式将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作。应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类库。常见的有两种适配器,分别是类适配器和对象适配器,这里拿更看好的对象适配器举例:<?phpnamespace Extend; /* * 对象适配器模式具体流程 * 1、根据需求定义接口,进而满足新需求功能 * 2、定义新类,继承并实现定义的接口 * 3、在实现接口时,原有的功能,只通过原有类对象调用原有类功能(委托) * 4、再根据需求,在新类中实现新需求功能 * 【适用性】 * (1)你想使用一个已经存在的类,而它的接口不符合你的需求 * (2)你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作 * (3)你想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口(仅限于对 / /* * 目标角色(根据需求定义含有旧功能加上新功能的接口) * Interface Target 我们期望得到的功能类 * @package Extend /interface Target{ public function simpleMethod1(); public function simpleMethod2();} /* * 源角色(在新功能提出之前的旧功能类和方法) * Class Adaptee * @package Extend /class Adaptee{ public function simpleMethod1() { echo ‘Adapter simpleMethod1’."<br>”; } } /* * 类适配器角色(新定义接口的具体实现) * Class Adapter * @package Extend /class Adapter implements Target{ private $adaptee; function __construct() { //适配器初始化直接new 原功能类,以方便之后委派 $adaptee = new Adaptee(); $this->adaptee = $adaptee; } //委派调用Adaptee的sampleMethod1方法 public function simpleMethod1() { echo $this->adaptee->simpleMethod1(); } public function simpleMethod2() { echo ‘Adapter simpleMethod2’."<br>"; } } /* * 客户端调用 /$adapter = new Adapter();$adapter->simpleMethod1();$adapter->simpleMethod2();这篇文章介绍了类适配器的使用,感兴趣的可以了解一下 https://segmentfault.com/a/11...2、策略模式将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式,策略模式可以实现依赖倒置以及控制反转。实例举例:假如一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示展示不同的广告。<?php /* * 首页数据控制器 * Class Index /class Home{ /* * 最好写上这个注释,告诉phpstorm是对应的哪个接口类,否则虽然程序执行正确,但phpstorm识别不了 * @var \Extend\UserType / protected $userType; /* * 首页展示数据 * 使用策略模式 * Index constructor. / function index() { echo “AD:”; $this->userType->showAd(); echo “Category:”; $this->userType->showCategory(); } /* * 策略模式 * 根据传递的用户性别展示不同类别数据 * @param \Extend\UserType $userType / function setUserType(\Extend\UserType $userType) { $this->userType = $userType; } } $obj = new Home();if ($_GET[‘userType’] == ‘female’){ $userType = new \Extend\FemaleUserType();} else { $userType = new \Extend\MaleUserType();}$obj->setUserType($userType);$obj->index();Extend/userType.php(定义的接口)<?php namespace Extend; /* * 策略模式 * 定义根据性别不同展示不同商品类目和广告接口 * Interface UserType * @package Extend /interface UserType{ //显示广告 function showAd(); //展示类目 function showCategory(); }MaleUserType.php、FemaleUserType.php(具体实现的类 )<?php namespace Extend; /* * 定义男性商品类目和广告位数据接口 * Class MaleUserType * @package Extend /class MaleUserType implements UserType{ /* * 广告栏数据展示 / function showAd() { echo “this is 男性’s 广告条目数据”; } /* * 商品类目数据展示 / function showCategory() { echo “this is 男性’s 商品类目数据”; } }<?php namespace Extend; /* * 定义女性商品类目和广告位数据接口 * Class FemaleUserType * @package Extend /class FemaleUserType implements UserType{ /* * 广告栏数据展示 / function showAd() { echo “this is 女性’s 广告条目数据”; } /* * 商品类目数据展示 / function showCategory() { echo “this is 女性’s 商品类目数据”; } }3、数据对象映射模式将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作。下面在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的sql语句映射成对象属性的操作。并结合使用数据对象映射模式、工厂模式、注册模式。 (1)数据库映射模式简单实例实现<?php //使用数据对象映射模式代替写sql$user = new Extend\User(25);$user->name = ‘小卜丢饭团子’;$user->salary = ‘20000’;$user->city = ‘浙江省’; Extend/User.php<?php namespace Extend; class User{ //对应数据库中的4个字段 public $id; public $name; public $salary; public $city; //存储数据库连接对象属性 protected $pdo; public $data; function __construct($id) { $this->id = $id; $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); } function __destruct() { $this->pdo->query(“update user set name = ‘{$this->name}’,salary = ‘{$this->salary}’,city = ‘{$this->city}’ where id=’{$this->id}’”); }}这样,执行index.php文件,数据库就会发生相应的操作,也就实现了基本的数据对象映射。(2)数据库映射模式复杂案例实现<?php class EX{ function index() { //使用数据对象映射模式代替写sql $user = Extend\Factory::getUserObj(25); $user->name = ‘小卜丢饭团子’; $user->salary = ‘20000’; $user->city = ‘浙江省’; } function test() { $user = Extend\Factory::getUserObj(25); $user->city = ‘广东省’; } } $ex = new EX();$ex->index();Extend/Factory.php<?php namespace Extend; class Factory{ /* * 工厂模式创建数据库对象,单例模式保证创建唯一db对象 * @return mixed / static function CreateDatabaseObj() { $db = Database::getInstance(); return $db; } /* * 工厂模式创建user对象,注册树模式保证创建唯一对象,避免资源浪费 * @param $id * @return User|mixed / static function getUserObj($id) { $key = ‘user’.$id; $user = RegisterTree::getObject($key); if (!$user) { $user = new User($id); RegisterTree::setObject($key,$user); } return $user; }}Extend/Register.php<?php namespace Extend; /* * 注册树模式 * Class RegisterTree * @package Extend /class RegisterTree{ static protected $objects;//静态类属性,用于储存注册到注册树上的对象 /* * 将对象注册到注册树上 * @param $alias 对象的别名 * @param $object 对象 / static function setObject($alias,$object) { self::$objects[$alias] = $object; } /* * 从注册树上取出给定别名相应的对象 * @param $alias 将对象插入到注册树上时写的别名 * @return mixed 对象 / static function getObject($alias) { return self::$objects[$alias]; } /* * 将对象从注册树上删除 * @param $alias 将对象插入到注册树上时写的别名 / public function unsetObject($alias) { unset(self::$objects[$alias]); } }Extend/User.php<?php namespace Extend; class User{ //对应数据库中的4个字段 public $id; public $name; public $salary; public $city; //存储数据库连接对象属性 protected $pdo; public $data; function __construct($id) { $this->id = $id; $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); } function __destruct() { $this->pdo->query(“update user set name = ‘{$this->name}’,salary = ‘{$this->salary}’,city = ‘{$this->city}’ where id=’{$this->id}’”); }}这样,就实现了稍复杂的数据对象映射模式和工厂模式、注册树模式相结合的案例。 4、观察者模式当一个对象状态发生改变时,依赖它的对象会全部收到通知,并自动更新。场景:一个事件发生后,要执行一连串更新操作。传统的编程方式就是在事件的代码之后直接加入处理逻辑,当更新的逻辑增多之后,代码会变的难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体的代码。观察者模式实现了低耦合,非侵入式的通知与更新机制。 4.1、传统模式举例:<?php/* * 一个事件的逻辑控制器 * Class Event /class Event{ /* * 用户确认订单 / function firmOrder() { //这里假设一个事件发生了,比如用户已经完成下单 echo “用户已下单<br>”; //传统方式是在发生一个事件之后直接进行一系列的相关处理,耦合度比较高,比如写入日志,给用户发邮件等等 echo “在用户下单之后进行的一系列操作<br>”; } } $event = new Event();$event->firmOrder();4.2、观察者模式典型实现方式:(1)定义2个接口:观察者(通知)接口、被观察者(主题)接口(2)定义2个类,观察者类实现观察者接口、被观察者类实现被观察者接口(3)被观察者注册自己需要通知的观察者(4)被观察者类某个业务逻辑发生时,通知观察者对象,进而每个观察者执行自己的业务逻辑。代码示例:<?php/* * 观察者模式场景描述: * 1、购票后记录文本日志 * 2、购票后记录数据库日志 * 3、购票后发送短信 * 4、购票送抵扣卷、兑换卷、积分 * 5、其他各类活动等 / /* * 观察者接口 /interface TicketObserver{ function buyTicketOver($sender, $args); //得到通知后调用的方法} /* * 被观察者接口(购票主题接口) /interface TicketObserved{ function addObserver($observer); //提供注册观察者方法} /* * 主体逻辑,继承被观察者接口 * Class BuyTicket /class BuyTicket implements TicketObserved{ /* * 定义观察者数组属性,用于储存观察者 * @var array / private $observers = array(); /* * 实现被观察者接口定义的方法(添加观察者) * @param $observer 实例化后的观察者对象 / public function addObserver($observer) { $this->observers[] = $observer; } /* * 购票主体方法 * BuyTicket constructor. * @param $ticket 购票排号 / public function buyTicket($ticket) { //1、根据需求写购票逻辑 //………….. //2、购票成功之后,循环通知观察者,并调用其buyTicketOver实现不同业务逻辑 foreach ($this->observers as $observe) { $observe->buyTicketOver($this, $ticket); //$this 可用来获取主题类句柄,在通知中使用 } } } /* * 购票成功后,发送短信通知 * Class buyTicketMSN /class buyTicketMSN implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 短信日志记录:购票成功:$ticket<br>"); }} /* * 购票成功后,记录日志 * Class buyTicketLog /class buyTicketLog implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 文本日志记录:购票成功:$ticket<br>"); }} /* * 购票成功后,赠送优惠券 * Class buyTicketCoupon /class buyTicketCoupon implements TicketObserver{ public function buyTicketOver($sender, $ticket) { echo (date ( ‘Y-m-d H:i:s’ ) . " 赠送优惠券:购票成功:$ticket 赠送10元优惠券1张。<br>"); }} //实例化购票类$buy = new BuyTicket();//添加多个观察者$buy->addObserver(new buyTicketMSN());$buy->addObserver(new buyTicketLog());$buy->addObserver(new buyTicketCoupon());//开始购票$buy->buyTicket (“7排8号”);5、原型模式原型模式与工厂模式的作用类似,都是用来创建对象的。但是实现方式是不同的。原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样,就免去了类创建时重复的初始化操作。原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需内存拷贝即可。代码实例:<?php/* * 抽象原型角色 /interface Prototype{ public function copy();} /* * 具体原型角色 /class ConcretePrototype implements Prototype{ private $_name; public function __construct($name) { $this->_name = $name; } public function setName($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function copy() { //深拷贝实现 //$serialize_obj = serialize($this); // 序列化 //$clone_obj = unserialize($serialize_obj); // 反序列化 //return $clone_obj; // 浅拷贝实现 return clone $this; } } /* * 测试深拷贝用的引用类 /class Demo{ public $array;} //测试$demo = new Demo();$demo->array = array(1, 2);$object1 = new ConcretePrototype($demo);$object2 = $object1->copy(); var_dump($object1->getName());echo ‘<br />’;var_dump($object2->getName());echo ‘<br />’; $demo->array = array(3, 4);var_dump($object1->getName());echo ‘<br />’;var_dump($object2->getName());echo ‘<br />’;关于原型模式文章:https://www.imooc.com/article…6、装饰器模式可以动态的添加或修改类的功能一个类实现一个功能,如果要再修改或添加额外的功能,传统的编程模式需要写一个子类继承它,并重新实现类的方法。使用装饰器模式,仅需在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性。<?php/* * 装饰器流程 * 1、声明装饰器接口(装饰器接口) * 2、具体类继承并实现装饰器接口(颜色装饰器实现,字体大小装饰器实现) * 3、在被装饰者类中定义"添加装饰器"方法(EchoText类中的addDecorator方法) * 4、在被装饰者类中定义调用装饰器的方法(EchoText类中的beforeEcho和afterEcho方法) * 5、使用时,实例化被装饰者类,并传入装饰器对象(比如new ColorDecorator(‘yellow’)) / /* * 装饰器接口 * Class Decorator /interface Decorator{ public function beforeEcho(); public function afterEcho();} /* * 颜色装饰器实现 * Class ColorDecorator /class ColorDecorator implements Decorator{ protected $color; public function __construct($color) { $this->color = $color; } public function beforeEcho() { echo “<dis style=‘color: {$this->color}’>”; } public function afterEcho() { echo “</div>”; }} /* * 字体大小装饰器实现 * Class SizeDecorator /class SizeDecorator implements Decorator{ protected $size; public function __construct($size) { $this->size = $size; } public function beforeEcho() { echo “<dis style=‘font-size: {$this->size}px’>”; } public function afterEcho() { echo “</div>”; }} /* * 被装饰者 * 输出一个字符串 * 装饰器动态添加功能 * Class EchoText /class EchoText{ protected $decorators = array();//存放装饰器 //装饰方法 public function Index() { //调用装饰器前置操作 $this->beforeEcho(); echo “你好,我是装饰器。”; //调用装饰器后置操作 $this->afterEcho(); } //添加装饰器 public function addDecorator(Decorator $decorator) { $this->decorators[] = $decorator; } //执行装饰器前置操作 先进先出原则 protected function beforeEcho() { foreach ($this->decorators as $decorator) $decorator->beforeEcho(); } //执行装饰器后置操作 先进后出原则 protected function afterEcho() { $tmp = array_reverse($this->decorators); foreach ($tmp as $decorator) $decorator->afterEcho(); }} //实例化输出类$echo = new EchoText();//增加装饰器$echo->addDecorator(new ColorDecorator(‘yellow’));//增加装饰器$echo->addDecorator(new SizeDecorator(‘22’));//输出$echo->Index();7、迭代器模式在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素而又不暴露该对象的内部表示,这就是PHP迭代器模式的定义。相对于传统编程模式,迭代器模式可以隐藏遍历元素的所需的操作。<?php$users = new Extend\AllUser();//循环遍历出所有用户数据foreach ($users as $user) { var_dump($user);}Extend/AllUser.php<?phpnamespace Extend; /* * 迭代器模式,继承php内部自带的迭代器接口(\Iterator) * Class AllUser * @package Extend /class AllUser implements \Iterator{ protected $index = 0;//表示索引 protected $ids = array();//用于储存所有user的id(实际应用中,可以采用注册树模式进行存储) protected $pdo;//用于存储数据库对象 function __construct() { //获取pdo数据库对象 $this->pdo = new \PDO(‘mysql:host=127.0.0.1;dbname=test’,‘root’,‘123456’); //获取所有用户的id $this->ids = $this->pdo->query(“select id from user”)->fetchAll(2); } /* * 实现接口方法,重置迭代器,回到集合开头 / public function rewind() { $this->index = 0; } /* * 实现接口方法,获取当前元素 * @return mixed|void / public function current() { $id = $this->ids[$this->index][‘id’]; //获取当前用户的数据 $user_data = $this->pdo->query(“select * from user where id=’{$id}’”)->fetch(2); return $user_data; } /* * 实现接口方法,获取当前元素键值 * @return mixed|void / public function key() { return $this->index; } /* * 实现接口方法,获取下一个元素 / public function next() { $this->index++; } /* * 实现接口方法,验证是否还有下一个元素 * @return bool|void */ public function valid() { return $this->index < count($this->ids); } }关于php迭代器文章 http://www.php.cn/php-weiziji… 8、代理模式在客户端与实体之间建立一个代理对象(proxy),客户端对实体进行操作全部委派给代理对象,隐藏实体的具体实现细节。典型的应用就是mysql的主从结构,读写分离。在mysql中,对所有读的操作请求从库,所有写的操作请求主库。声明一个代理类,前台使用时只需创建一个代理类,调用对应方法即可。代码实例:<?php // 1、传统编程模式是手动选择#查询操作使用从库//$db_slave = Extend\Factory::getDatabase(‘slave’);//$info = $db_slave->query(“select * from user where id = 1 limit 1”);#增删改操作使用主库//$db_master = Extend\Factory::getDatabase(‘master’);//$db_master->query(“update user name = ‘xiaobudiu’ where id = 29 limit 1”); // 2、使用代理模式$db_proxy = new Extend\Proxy();$db_proxy->getUserName(1);$db_proxy->setUserName(29,‘xiaobudiu’);Extend/Proxy.php<?phpnamespace Extend; class Proxy implements IUserProxy{ function getUserName($id) { $db = Factory::getDatabase(‘slave’); $db->query(“select name from user where id =$id limit 1”); } function setUserName($id, $name) { $db = Factory::getDatabase(‘master’); $db->query(“update user set name = $name where id =$id limit 1”); }}Extend/Factory.php<?phpnamespace Extend; class Factory{ static function getDatabase($id) { $key = ‘database’.$id; if ($id == ‘slave’) { $slaves = Application::getInstance()->config[‘database’][‘slave’]; $db_conf = $slaves[array_rand($slaves)]; } else { $db_conf = Application::getInstance()->config[‘database’][$id]; } //注册树模式存储及获取对象 $db = Register::get($key); if (!$db) { $db = new Database\MySQLi(); $db->connect($db_conf[‘host’], $db_conf[‘user’], $db_conf[‘password’], $db_conf[‘dbname’]); Register::set($key, $db); } return $db; } }Extend/Application.php<?phpnamespace Extend; class Application{ public $base_dir; protected static $instance; public $config; protected function __construct($base_dir) { $this->base_dir = $base_dir; $this->config = new Config($base_dir.’/configs’); } static function getInstance($base_dir = ‘’) { if (empty(self::$instance)) { self::$instance = new self($base_dir); } return self::$instance; } }Extend/Config.php<?phpnamespace Extend; /** * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend /class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function __construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }}configs/database.php<?php$config = array( ‘master’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave’ => array( ‘slave1’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave2’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ),);return $config;关于php代理模式文章 https://segmentfault.com/a/11…五、其余设计模式以及总结文章接:https://segmentfault.com/a/11…https://segmentfault.com/a/11… 六、面向对象编程的基本原则1、单一职责原则:一个类只需要做好一件事情。不要使用一个类完成很多功能,而应该拆分成更多更小的类。2、开放封闭原则:一个类写好之后,应该是可扩展而不可修改的。3、依赖倒置原则:一个类不应该强依赖另外一个类,每个类对于另外一个类都是可替换的。4、配置化原则:尽量使用配置,而不是硬编码。5、面向接口编程原则:只需要关心某个类提供了哪些接口,而不需要关心他的实现。 七、自动加载配置类文件1、php中使用ArrayAccess实现配置文件的加载(使得程序可以以数组的方式进行读取配置)(1)定义Config.php,继承php自带的ArrayAccess接口,并实现相应的方法,用于读取和设置配置Extend/Config.php<?phpnamespace Extend; /* * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend /class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function _construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }}(2)configs/database.php<?php$config = array( ‘master’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave’ => array( ‘slave1’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ‘slave2’ => array( ’type’ => ‘MySQL’, ‘host’ => ‘127.0.0.1’, ‘user’ => ‘root’, ‘password’ => ‘123456’, ‘dbname’ => ’test’, ), ),);return $config;(3)读取配置<?php $config = new Extend\Config(DIR.’/configs’);var_dump($config[‘database’]);到此,就可以在程序中随心所欲的加载配置文件了。2、在工厂方法中读取配置,生成可配置化的对象Extend/Factory.php<?phpnamespace Extend; class Factory{ static function getDatabase($id) { $key = ‘database’.$id; if ($id == ‘slave’) { $slaves = Application::getInstance()->config[‘database’][‘slave’]; $db_conf = $slaves[array_rand($slaves)]; } else { $db_conf = Application::getInstance()->config[‘database’][$id]; } //注册树模式存储及获取对象 $db = Register::get($key); if (!$db) { $db = new Database\MySQLi(); $db->connect($db_conf[‘host’], $db_conf[‘user’], $db_conf[‘password’], $db_conf[‘dbname’]); Register::set($key, $db); } return $db; } }Extend/Application.php<?phpnamespace Extend; class Application{ public $base_dir; protected static $instance; public $config; protected function __construct($base_dir) { $this->base_dir = $base_dir; $this->config = new Config($base_dir.’/configs’); } static function getInstance($base_dir = ‘’) { if (empty(self::$instance)) { self::$instance = new self($base_dir); } return self::$instance; } }Extend/Config.php<?phpnamespace Extend; /* * 配置类,继承于php自带的ArrayAccess接口 * 允许一个对象以数组的方式访问 * Class Config * @package Extend */class Config implements \ArrayAccess{ protected $path; protected $configs = array(); function __construct($path) { $this->path = $path; } function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.’/’.$key.’.php’; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } function offsetSet($key, $value) { throw new \Exception(“cannot write config file.”); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); }} ...

January 19, 2019 · 11 min · jiezi

PHP设计模式范例 — DesignPatternsPHP(2)结构型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我2、结构型设计模式在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。2.1 适配器模式2.1.1 目的将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。2.1.2 例子数据库客户端库适配器使用不同的webservices,通过适配器来标准化输出数据,从而保证不同webservice输出的数据是一致的2.1.3 UML图2.1.4 代码你可以在 GitHub 上找到这些代码BookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface BookInterface{ public function turnPage(); public function open(); public function getPage(): int;}Book.php<?phpnamespace DesignPatterns\Structural\Adapter;class Book implements BookInterface{ /** * @var int / private $page; public function open() { $this->page = 1; } public function turnPage() { $this->page++; } public function getPage(): int { return $this->page; }}EBookAdapter.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * This is the adapter here. Notice it implements BookInterface, * therefore you don’t have to change the code of the client which is using a Book /class EBookAdapter implements BookInterface{ /* * @var EBookInterface / protected $eBook; /* * @param EBookInterface $eBook / public function __construct(EBookInterface $eBook) { $this->eBook = $eBook; } /* * This class makes the proper translation from one interface to another. / public function open() { $this->eBook->unlock(); } public function turnPage() { $this->eBook->pressNext(); } /* * notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface * supports only a current page getter, so we adapt the behavior here * * @return int / public function getPage(): int { return $this->eBook->getPage()[0]; }}EBookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface EBookInterface{ public function unlock(); public function pressNext(); /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array;}Kindle.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * this is the adapted class. In production code, this could be a class from another package, some vendor code. * Notice that it uses another naming scheme and the implementation does something similar but in another way /class Kindle implements EBookInterface{ /* * @var int / private $page = 1; /* * @var int / private $totalPages = 100; public function pressNext() { $this->page++; } public function unlock() { } /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array { return [$this->page, $this->totalPages]; }}2.2 桥接模式2.2.1 目的解耦一个对象的实现与抽象,这样两者可以独立地变化。2.2.2 例子Symfony DoctrineBridge2.2.3 UML图2.2.4 代码你可以在 GitHub 上找到这些代码Formatter.php<?phpnamespace DesignPatterns\Structural\Bridge;interface Formatter{ public function format(string $text): string;}PlainTextFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class PlainTextFormatter implements Formatter{ public function format(string $text): string { return $text; }}HtmlFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class HtmlFormatter implements Formatter{ public function format(string $text): string { return sprintf(’<p>%s</p>’, $text); }}Service.php<?phpnamespace DesignPatterns\Structural\Bridge;abstract class Service{ /* * @var Formatter / protected $implementation; /* * @param Formatter $printer / public function __construct(Formatter $printer) { $this->implementation = $printer; } /* * @param Formatter $printer / public function setImplementation(Formatter $printer) { $this->implementation = $printer; } abstract public function get(): string;}HelloWorldService.php<?phpnamespace DesignPatterns\Structural\Bridge;class HelloWorldService extends Service{ public function get(): string { return $this->implementation->format(‘Hello World’); }}PingService.php<?phpnamespace DesignPatterns\Structural\Bridge;class PingService extends Service{ public function get(): string { return $this->implementation->format(‘pong’); }}2.3 组合模式2.3.1 目的以单个对象的方式来对待一组对象2.3.2 例子form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用render()方法时,它会历遍所有的子元素,调用render()方法Zend_Config: 配置选项树, 其每一个分支都是Zend_Config对象2.3.3 UML图2.3.4 代码你可以在 GitHub 上找到这些代码RenderableInterface.php<?phpnamespace DesignPatterns\Structural\Composite;interface RenderableInterface{ public function render(): string;}Form.php<?phpnamespace DesignPatterns\Structural\Composite;/* * The composite node MUST extend the component contract. This is mandatory for building * a tree of components. /class Form implements RenderableInterface{ /* * @var RenderableInterface[] / private $elements; /* * runs through all elements and calls render() on them, then returns the complete representation * of the form. * * from the outside, one will not see this and the form will act like a single object instance * * @return string / public function render(): string { $formCode = ‘<form>’; foreach ($this->elements as $element) { $formCode .= $element->render(); } $formCode .= ‘</form>’; return $formCode; } /* * @param RenderableInterface $element / public function addElement(RenderableInterface $element) { $this->elements[] = $element; }}InputElement.php<?phpnamespace DesignPatterns\Structural\Composite;class InputElement implements RenderableInterface{ public function render(): string { return ‘<input type=“text” />’; }}TextElement.php<?phpnamespace DesignPatterns\Structural\Composite;class TextElement implements RenderableInterface{ /* * @var string / private $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; }}2.4 数据映射器2.4.1 目的数据映射器是一个数据访问层,用于将数据在持久性数据存储(通常是一个关系数据库)和内存中的数据表示(领域层)之间进行相互转换。其目的是为了将数据的内存表示、持久存储、数据访问进行分离。该层由一个或者多个映射器组成(或者数据访问对象),并且进行数据的转换。映射器的实现在范围上有所不同。通用映射器将处理许多不同领域的实体类型,而专用映射器将处理一个或几个。此模式的主要特点是,与Active Record不同,其数据模式遵循单一职责原则(Single Responsibility Principle)。2.4.2 例子DB对象关系映射器(ORM): Doctrine2使用“EntityRepository”作为DAO2.4.3 UML图2.4.4 代码你可以在 GitHub 上找到这些代码User.php<?phpnamespace DesignPatterns\Structural\DataMapper;class User{ /* * @var string / private $username; /* * @var string / private $email; public static function fromState(array $state): User { // validate state before accessing keys! return new self( $state[‘username’], $state[’email’] ); } public function __construct(string $username, string $email) { // validate parameters before setting them! $this->username = $username; $this->email = $email; } /* * @return string / public function getUsername() { return $this->username; } /* * @return string / public function getEmail() { return $this->email; }}UserMapper.php<?phpnamespace DesignPatterns\Structural\DataMapper;class UserMapper{ /* * @var StorageAdapter / private $adapter; /* * @param StorageAdapter $storage / public function __construct(StorageAdapter $storage) { $this->adapter = $storage; } /* * finds a user from storage based on ID and returns a User object located * in memory. Normally this kind of logic will be implemented using the Repository pattern. * However the important part is in mapRowToUser() below, that will create a business object from the * data fetched from storage * * @param int $id * * @return User / public function findById(int $id): User { $result = $this->adapter->find($id); if ($result === null) { throw new \InvalidArgumentException(“User #$id not found”); } return $this->mapRowToUser($result); } private function mapRowToUser(array $row): User { return User::fromState($row); }}StorageAdapter.php<?phpnamespace DesignPatterns\Structural\DataMapper;class StorageAdapter{ /* * @var array / private $data = []; public function __construct(array $data) { $this->data = $data; } /* * @param int $id * * @return array|null / public function find(int $id) { if (isset($this->data[$id])) { return $this->data[$id]; } return null; }}2.5 装饰器2.5.1 目的动态地为类的实例添加功能2.5.2 例子Zend Framework: Zend_Form_Element 实例的装饰器Web Service层:REST服务的JSON与XML装饰器(当然,在此只能使用其中的一种)2.5.3 UML图2.5.4 代码你可以在 GitHub 上找到这些代码Booking.php<?phpnamespace DesignPatterns\Structural\Decorator;interface Booking{ public function calculatePrice(): int; public function getDescription(): string;}BookingDecorator.php<?phpnamespace DesignPatterns\Structural\Decorator;abstract class BookingDecorator implements Booking{ /* * @var Booking / protected $booking; public function __construct(Booking $booking) { $this->booking = $booking; }}DoubleRoomBooking.php<?phpnamespace DesignPatterns\Structural\Decorator;class DoubleRoomBooking implements Booking{ public function calculatePrice(): int { return 40; } public function getDescription(): string { return ‘double room’; }}ExtraBed.php<?phpnamespace DesignPatterns\Structural\Decorator;class ExtraBed extends BookingDecorator{ private const PRICE = 30; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with extra bed’; }}WiFi.php<?phpnamespace DesignPatterns\Structural\Decorator;class WiFi extends BookingDecorator{ private const PRICE = 2; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with wifi’; }}2.6 依赖注入2.6.1 目的实现了松耦合的软件架构,可得到更好的测试,管理和扩展的代码2.6.2 用例注入DatabaseConfiguration, DatabaseConnection将从$config获得所需的所有内容。没有DI(依赖注入),配置将直接在DatabaseConnection中创建,这不利于测试和扩展它。2.6.3 例子Doctrine2 ORM 使用了依赖注入,它通过配置注入了 Connection 对象。为了达到方便测试的目的,可以很容易的通过配置创建一个mock的 Connection 对象。Symfony 和 Zend Framework 2 也有了专门的依赖注入容器,用来通过配置数据创建需要的对象(比如在控制器中使用依赖注入容器获取所需的对象)2.6.4 UML图2.6.5 代码你可以在 GitHub 上找到这些代码DatabaseConfiguration.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConfiguration{ /* * @var string / private $host; /* * @var int / private $port; /* * @var string / private $username; /* * @var string / private $password; public function __construct(string $host, int $port, string $username, string $password) { $this->host = $host; $this->port = $port; $this->username = $username; $this->password = $password; } public function getHost(): string { return $this->host; } public function getPort(): int { return $this->port; } public function getUsername(): string { return $this->username; } public function getPassword(): string { return $this->password; }}DatabaseConnection.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConnection{ /* * @var DatabaseConfiguration / private $configuration; /* * @param DatabaseConfiguration $config / public function __construct(DatabaseConfiguration $config) { $this->configuration = $config; } public function getDsn(): string { // this is just for the sake of demonstration, not a real DSN // notice that only the injected config is used here, so there is // a real separation of concerns here return sprintf( ‘%s:%s@%s:%d’, $this->configuration->getUsername(), $this->configuration->getPassword(), $this->configuration->getHost(), $this->configuration->getPort() ); }}2.7 外观模式2.7.1 目的Facade模式的主要目标不是避免您必须阅读复杂API的手册。这只是副作用。主要目的是减少耦合并遵循Demeter定律。Facade通过嵌入多个(当然,有时只有一个)接口来解耦访客与子系统,当然也降低复杂度。Facade不会禁止你访问子系统你可以为一个子系统提供多个 Facade因此一个好的 Facade 里面不会有 new 。如果每个方法里都要构造多个对象,那么它就不是 Facade,而是生成器或者 [ 抽象 | 静态 | 简单 ] 工厂方法。优秀的 Facade 不会有 new,并且构造函数参数是接口类型的。如果你需要创建一个新实例,则在参数中传入一个工厂对象。2.7.2 UML图2.7.3 代码你可以在 GitHub 上找到这些代码Facade.php<?phpnamespace DesignPatterns\Structural\Facade;class Facade{ /* * @var OsInterface / private $os; /* * @var BiosInterface / private $bios; /* * @param BiosInterface $bios * @param OsInterface $os / public function __construct(BiosInterface $bios, OsInterface $os) { $this->bios = $bios; $this->os = $os; } public function turnOn() { $this->bios->execute(); $this->bios->waitForKeyPress(); $this->bios->launch($this->os); } public function turnOff() { $this->os->halt(); $this->bios->powerDown(); }}OsInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface OsInterface{ public function halt(); public function getName(): string;}BiosInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface BiosInterface{ public function execute(); public function waitForKeyPress(); public function launch(OsInterface $os); public function powerDown();}2.8 连贯接口2.8.1 目的用来编写易于阅读的代码,就像自然语言一样(如英语)2.8.2 例子Doctrine2 的 QueryBuilder,就像下面例子中类似PHPUnit 使用连贯接口来创建 mock 对象Yii 框架:CDbCommand 与 CActiveRecord 也使用此模式2.8.3 UML图2.8.4 代码你可以在 GitHub 上找到这些代码Sql.php<?phpnamespace DesignPatterns\Structural\FluentInterface;class Sql{ /* * @var array / private $fields = []; /* * @var array / private $from = []; /* * @var array / private $where = []; public function select(array $fields): Sql { $this->fields = $fields; return $this; } public function from(string $table, string $alias): Sql { $this->from[] = $table.’ AS ‘.$alias; return $this; } public function where(string $condition): Sql { $this->where[] = $condition; return $this; } public function __toString(): string { return sprintf( ‘SELECT %s FROM %s WHERE %s’, join(’, ‘, $this->fields), join(’, ‘, $this->from), join(’ AND ‘, $this->where) ); }}2.9 享元2.9.1 目的为了尽可能减少内存使用,Flyweight与类似的对象共享尽可能多的内存。当使用大量状态相差不大的对象时,就需要它。通常的做法是保持外部数据结构中的状态,并在需要时将其传递给flyweight对象。2.9.2 UML图2.9.3 代码你可以在 GitHub 上找到这些代码FlyweightInterface.php<?phpnamespace DesignPatterns\Structural\Flyweight;interface FlyweightInterface{ public function render(string $extrinsicState): string;}CharacterFlyweight.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * Implements the flyweight interface and adds storage for intrinsic state, if any. * Instances of concrete flyweights are shared by means of a factory. /class CharacterFlyweight implements FlyweightInterface{ /* * Any state stored by the concrete flyweight must be independent of its context. * For flyweights representing characters, this is usually the corresponding character code. * * @var string / private $name; public function __construct(string $name) { $this->name = $name; } public function render(string $font): string { // Clients supply the context-dependent information that the flyweight needs to draw itself // For flyweights representing characters, extrinsic state usually contains e.g. the font. return sprintf(‘Character %s with font %s’, $this->name, $font); }}FlyweightFactory.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * A factory manages shared flyweights. Clients should not instantiate them directly, * but let the factory take care of returning existing objects or creating new ones. /class FlyweightFactory implements \Countable{ /* * @var CharacterFlyweight[] / private $pool = []; public function get(string $name): CharacterFlyweight { if (!isset($this->pool[$name])) { $this->pool[$name] = new CharacterFlyweight($name); } return $this->pool[$name]; } public function count(): int { return count($this->pool); }}2.10 代理模式2.10.1 目的为昂贵或者无法复制的资源提供接口。2.10.2 例子Doctrine2 使用代理来实现框架特性(如延迟初始化),同时用户还是使用自己的实体类并且不会使用或者接触到代理2.10.3 UML图2.10.4 代码你可以在 GitHub 上找到这些代码BankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;interface BankAccount{ public function deposit(int $amount); public function getBalance(): int;}HeavyBankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;class HeavyBankAccount implements BankAccount{ /* * @var int[] / private $transactions = []; public function deposit(int $amount) { $this->transactions[] = $amount; } public function getBalance(): int { // this is the heavy part, imagine all the transactions even from // years and decades ago must be fetched from a database or web service // and the balance must be calculated from it return array_sum($this->transactions); }}BankAccountProxy.php<?phpnamespace DesignPatterns\Structural\Proxy;class BankAccountProxy extends HeavyBankAccount implements BankAccount{ /* * @var int / private $balance; public function getBalance(): int { // because calculating balance is so expensive, // the usage of BankAccount::getBalance() is delayed until it really is needed // and will not be calculated again for this instance if ($this->balance === null) { $this->balance = parent::getBalance(); } return $this->balance; }}2.11 注册模式2.11.1 目的要为整个应用程序中经常使用的对象实现中央存储,通常只使用静态方法(或使用单例模式)的抽象类来实现。请记住,这将引入全局状态,这在任何时候都应该避免!而是使用依赖注入来实现它!2.11.2 例子Zend Framework 1: Zend_Registry 持有应用的logger对象,前端控制器等。Yii 框架: CWebApplication 持有所有的应用组件,如 CWebUser, CUrlManager, 等。2.11.3 UML图2.11.4 代码你可以在 GitHub 上找到这些代码Registry.php<?phpnamespace DesignPatterns\Structural\Registry;abstract class Registry{ const LOGGER = ’logger’; /* * this introduces global state in your application which can not be mocked up for testing * and is therefor considered an anti-pattern! Use dependency injection instead! * * @var array / private static $storedValues = []; /* * @var array / private static $allowedKeys = [ self::LOGGER, ]; /* * @param string $key * @param mixed $value * * @return void / public static function set(string $key, $value) { if (!in_array($key, self::$allowedKeys)) { throw new \InvalidArgumentException(‘Invalid key given’); } self::$storedValues[$key] = $value; } /* * @param string $key * * @return mixed */ public static function get(string $key) { if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) { throw new \InvalidArgumentException(‘Invalid key given’); } return self::$storedValues[$key]; }}相关文章:PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式 ...

January 18, 2019 · 9 min · jiezi

PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我1、创建型设计模式在软件工程中,创建型设计模式承担着对象创建的职责,尝试创建适合程序上下文的对象,对象创建设计模式的产生是由于软件工程设计的问题,具体说是向设计中增加复杂度,创建型设计模式解决了程序设计中对象创建的问题。1.1 抽象工厂1.1.1 目的创建一系列相关或依赖的对象,而不指定它们的具体类。通常创建的类都实现相同的接口。抽象工厂的客户端并不关心这些对象是如何创建的,它只知道它们是如何组合在一起的。1.1.2 UML图1.1.3 代码你可以在 GitHub 上查看代码Parser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;interface Parser{ public function parse(string $input): array;}CsvParser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class CsvParser implements Parser{ const OPTION_CONTAINS_HEADER = true; const OPTION_CONTAINS_NO_HEADER = false; /** * @var bool / private $skipHeaderLine; public function __construct(bool $skipHeaderLine) { $this->skipHeaderLine = $skipHeaderLine; } public function parse(string $input): array { $headerWasParsed = false; $parsedLines = []; foreach (explode(PHP_EOL, $input) as $line) { if (!$headerWasParsed && $this->skipHeaderLine === self::OPTION_CONTAINS_HEADER) { $headerWasParsed = true; continue; } $parsedLines[] = str_getcsv($line); } return $parsedLines; }}JsonParser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class JsonParser implements Parser{ public function parse(string $input): array { return json_decode($input, true); }}ParserFactory.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class ParserFactory{ public function createCsvParser(bool $skipHeaderLine): CsvParser { return new CsvParser($skipHeaderLine); } public function createJsonParser(): JsonParser { return new JsonParser(); }}1.2 生成器模式1.2.1 目的生成器的目的是将复杂对象的创建过程(流程)进行抽象,生成器表现为接口的形式。在特定的情况下,比如如果生成器对将要创建的对象有足够多的了解,那么代表生成器的接口 interface 可以是一个抽象类(也就是说可以有一定的具体实现,就像众所周知的适配器模式)。如果对象有复杂的继承树,理论上创建对象的生成器也同样具有复杂的继承树。提示:生成器通常具有流畅的接口,推荐阅读关于 PHPUnit 的 mock 生成器获取更好的理解。1.2.2 例子PHPUnit: Mock 生成器1.2.3 UML图1.2.4 代码你可以在 GitHub 上找到这些代码Director.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;/* * Director is part of the builder pattern. It knows the interface of the builder * and builds a complex object with the help of the builder * * You can also inject many builders instead of one to build more complex objects /class Director{ public function build(BuilderInterface $builder): Vehicle { $builder->createVehicle(); $builder->addDoors(); $builder->addEngine(); $builder->addWheel(); return $builder->getVehicle(); }}BuilderInterface.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;interface BuilderInterface{ public function createVehicle(); public function addWheel(); public function addEngine(); public function addDoors(); public function getVehicle(): Vehicle;}TruckBuilder.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;class TruckBuilder implements BuilderInterface{ /* * @var Parts\Truck / private $truck; public function addDoors() { $this->truck->setPart(‘rightDoor’, new Parts\Door()); $this->truck->setPart(’leftDoor’, new Parts\Door()); } public function addEngine() { $this->truck->setPart(’truckEngine’, new Parts\Engine()); } public function addWheel() { $this->truck->setPart(‘wheel1’, new Parts\Wheel()); $this->truck->setPart(‘wheel2’, new Parts\Wheel()); $this->truck->setPart(‘wheel3’, new Parts\Wheel()); $this->truck->setPart(‘wheel4’, new Parts\Wheel()); $this->truck->setPart(‘wheel5’, new Parts\Wheel()); $this->truck->setPart(‘wheel6’, new Parts\Wheel()); } public function createVehicle() { $this->truck = new Parts\Truck(); } public function getVehicle(): Vehicle { return $this->truck; }}CarBuilder.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;class CarBuilder implements BuilderInterface{ /* * @var Parts\Car / private $car; public function addDoors() { $this->car->setPart(‘rightDoor’, new Parts\Door()); $this->car->setPart(’leftDoor’, new Parts\Door()); $this->car->setPart(’trunkLid’, new Parts\Door()); } public function addEngine() { $this->car->setPart(’engine’, new Parts\Engine()); } public function addWheel() { $this->car->setPart(‘wheelLF’, new Parts\Wheel()); $this->car->setPart(‘wheelRF’, new Parts\Wheel()); $this->car->setPart(‘wheelLR’, new Parts\Wheel()); $this->car->setPart(‘wheelRR’, new Parts\Wheel()); } public function createVehicle() { $this->car = new Parts\Car(); } public function getVehicle(): Vehicle { return $this->car; }}Parts/Vehicle.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;abstract class Vehicle{ /* * @var object[] / private $data = []; /* * @param string $key * @param object $value / public function setPart($key, $value) { $this->data[$key] = $value; }}Parts/Truck.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Truck extends Vehicle{}Parts/Car.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Engine{}Parts/Engine.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Engine{}Parts/Wheel.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Wheel{}Parts/Door.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Door{}1.3 工厂方法1.3.1 目的SimpleFactory的优点是您可以子类化它来实现创建对象的不同方法。对于简单的情况,这个抽象类可能只是一个接口。这个模式是一个 “真正” 的设计模式,因为它遵循了依赖反转原则 Dependency Inversion Principle 众所周知这个 “D” 代表了真正的面向对象程序设计。它意味着工厂方法类依赖于类的抽象,而不是具体将被创建的类,这是工厂方法模式与简单工厂模式和静态工厂模式最重要的区别。1.3.2 UML图1.3.3 代码你可以在 GitHub 上找到这些代码Logger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;interface Logger{ public function log(string $message);}StdoutLogger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class StdoutLogger implements Logger{ public function log(string $message) { echo $message; }}FileLogger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class FileLogger implements Logger{ /* * @var string / private $filePath; public function __construct(string $filePath) { $this->filePath = $filePath; } public function log(string $message) { file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND); }}LoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;interface LoggerFactory{ public function createLogger(): Logger;}StdoutLoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class StdoutLoggerFactory implements LoggerFactory{ public function createLogger(): Logger { return new StdoutLogger(); }}FileLoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class FileLoggerFactory implements LoggerFactory{ /* * @var string / private $filePath; public function __construct(string $filePath) { $this->filePath = $filePath; } public function createLogger(): Logger { return new FileLogger($this->filePath); }}1.4 多例多例模式已经被考虑列入到反模式中!请使用依赖注入获得更好的代码可测试性和可控性!1.4.1 目的使类仅有一个命名的对象的集合可供使用,像单例模式但是有多个实例。1.4.2 例子2 个数据库连接,比如,一个连接MySQL,另一个连接SQLite多个日志记录器(一个记录调试信息,另一个记录错误信息)1.4.3 UML 图1.4.4 代码你可以在 GitHub 上找到这些代码Multiton.php<?phpnamespace DesignPatterns\Creational\Multiton;final class Multiton{ const INSTANCE_1 = ‘1’; const INSTANCE_2 = ‘2’; /* * @var Multiton[] / private static $instances = []; /* * this is private to prevent from creating arbitrary instances / private function __construct() { } public static function getInstance(string $instanceName): Multiton { if (!isset(self::$instances[$instanceName])) { self::$instances[$instanceName] = new self(); } return self::$instances[$instanceName]; } /* * prevent instance from being cloned / private function __clone() { } /* * prevent instance from being unserialized / private function __wakeup() { }}1.5 对象池1.5.1 目的对象池设计模式 是创建型设计模式,它会对新创建的对象应用一系列的初始化操作,让对象保持立即可使用的状态 - 一个存放对象的 “池子” - 而不是对对象进行一次性的的使用(创建并使用,完成之后立即销毁)。对象池的使用者会对对象池发起请求,以期望获取一个对象,并使用获取到的对象进行一系列操作,当使用者对对象的使用完成之后,使用者会将由对象池的对象创建工厂创建的对象返回给对象池,而不是用完之后销毁获取到的对象。对象池在某些情况下会带来重要的性能提升,比如耗费资源的对象初始化操作,实例化类的代价很高,但每次实例化的数量较少的情况下。对象池中将被创建的对象会在真正被使用时被提前创建,避免在使用时让使用者浪费对象创建所需的大量时间(比如在对象某些操作需要访问网络资源的情况下)从池子中取得对象的时间是可预测的,但新建一个实例所需的时间是不确定。总之,对象池会为你节省宝贵的程序执行时间,比如像数据库连接,socket连接,大量耗费资源的代表数字资源的对象,像字体或者位图。不过,在特定情况下,简单的对象创建池(没有请求外部的资源,仅仅将自身保存在内存中)或许并不会提升效率和性能,这时候,就需要使用者酌情考虑了。1.5.2 UML图1.5.3 代码你可以在 GitHub 上找到这些代码WorkerPool.php<?phpnamespace DesignPatterns\Creational\Pool;class WorkerPool implements \Countable{ /* * @var StringReverseWorker[] / private $occupiedWorkers = []; /* * @var StringReverseWorker[] / private $freeWorkers = []; public function get(): StringReverseWorker { if (count($this->freeWorkers) == 0) { $worker = new StringReverseWorker(); } else { $worker = array_pop($this->freeWorkers); } $this->occupiedWorkers[spl_object_hash($worker)] = $worker; return $worker; } public function dispose(StringReverseWorker $worker) { $key = spl_object_hash($worker); if (isset($this->occupiedWorkers[$key])) { unset($this->occupiedWorkers[$key]); $this->freeWorkers[$key] = $worker; } } public function count(): int { return count($this->occupiedWorkers) + count($this->freeWorkers); }}StringReverseWorker.php<?phpnamespace DesignPatterns\Creational\Pool;class StringReverseWorker{ /* * @var \DateTime / private $createdAt; public function __construct() { $this->createdAt = new \DateTime(); } public function run(string $text) { return strrev($text); }}1.6 原型模式1.6.1 目的通过创建一个原型对象,然后复制原型对象来避免通过标准的方式创建大量的对象产生的开销(new Foo())。1.6.2 例子大量的数据对象(比如通过ORM获取1,000,000行数据库记录然后创建每一条记录对应的对象实体)1.6.3 UML图1.6.4 代码你可以在 GitHub 上找到这些代码BookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;abstract class BookPrototype{ /* * @var string / protected $title; /* * @var string / protected $category; abstract public function __clone(); public function getTitle(): string { return $this->title; } public function setTitle($title) { $this->title = $title; }}BarBookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;class BarBookPrototype extends BookPrototype{ /* * @var string / protected $category = ‘Bar’; public function __clone() { }}FooBookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;class FooBookPrototype extends BookPrototype{ /* * @var string / protected $category = ‘Foo’; public function __clone() { }}1.7 简单工厂1.7.1 目的它与静态工厂不同,因为它不是静态的。因此,可以有多个参数化的工厂,可以子类化它,也可以模拟它。它总是比静态工厂更受欢迎!1.7.2 UML图1.7.3 代码你可以在 GitHub 上找到这些代码SimpleFactory.php<?phpnamespace DesignPatterns\Creational\SimpleFactory;class SimpleFactory{ public function createBicycle(): Bicycle { return new Bicycle(); }}Bicycle.php<?phpnamespace DesignPatterns\Creational\SimpleFactory;class Bicycle{ public function driveTo(string $destination) { }}1.7.4 使用 $factory = new SimpleFactory(); $bicycle = $factory->createBicycle(); $bicycle->driveTo(‘Paris’);1.8 单例模式1.8.1 目标使应用中只存在一个对象的实例,并且使这个单实例负责所有对该对象的调用。1.8.2 例子数据库连接器日志记录器 (可能有多个实例,比如有多个日志文件因为不同的目的记录不同到的日志)应用锁文件 (理论上整个应用只有一个锁文件)1.8.3 UML图1.8.4 代码你可以在 GitHub 上找到这些代码Singleton.php<?phpnamespace DesignPatterns\Creational\Singleton;final class Singleton{ /* * @var Singleton / private static $instance; /* * gets the instance via lazy initialization (created on first usage) / public static function getInstance(): Singleton { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } /* * is not allowed to call from outside to prevent from creating multiple instances, * to use the singleton, you have to obtain the instance from Singleton::getInstance() instead / private function __construct() { } /* * prevent the instance from being cloned (which would create a second instance of it) / private function __clone() { } /* * prevent from being unserialized (which would create a second instance of it) / private function _wakeup() { }}1.9 静态工厂1.9.1 目的和抽象工厂类似,静态工厂模式用来创建一系列互相关联或依赖的对象,和抽象工厂模式不同的是静态工厂模式只用一个静态方法就解决了所有类型的对象创建,通常被命名为 Factory 或者 Generators1.9.2 例子Zend Framework: zend_cache 后端或 _Frontend 使用工厂方法创建缓存后端和前端1.9.3 UML图1.9.4 代码你可以在 GitHub 上找到这些代码StaticFactory.php<?phpnamespace DesignPatterns\Creational\StaticFactory;/* * Note1: Remember, static means global state which is evil because it can’t be mocked for tests * Note2: Cannot be subclassed or mock-upped or have multiple different instances. /final class StaticFactory{ /* * @param string $type * * @return Formatter */ public static function factory(string $type): Formatter { if ($type == ’number’) { return new FormatNumber(); } elseif ($type == ‘string’) { return new FormatString(); } throw new \InvalidArgumentException(‘Unknown format given’); }}Formatter.php<?phpnamespace DesignPatterns\Creational\StaticFactory;interface Formatter{ public function format(string $input): string;}FormatString.php<?phpnamespace DesignPatterns\Creational\StaticFactory;class FormatString implements Formatter{ public function format(string $input): string { return $input; }}FormatNumber.php<?phpnamespace DesignPatterns\Creational\StaticFactory;class FormatNumber implements Formatter{ public function format(string $input): string { return number_format($input); }} ...

January 18, 2019 · 6 min · jiezi

《JavaScript设计模式》阅读笔记_part2

JavaScript设计模式阅读更多文章查看本专栏设计模式第一篇:创建型设计模式1、简单工厂模式简单工厂模式:又叫静态工厂方法,有一个工厂对象决定创建某一种产品对象类的实例。主要用于创建同一类对象。根据现有的需求选择不同的东西。// 简单的工厂模式var redBall = function () { console.log(‘redBall’)};var greenBall = function () { console.log(‘greenBall’)};var blackBall = function () { console.log(‘blackBall’)};var blueBall = function () { console.log(‘blueBall’)};//工厂函数var ballFactory = function (type) { switch (type) { case ‘red’: return new redBall(); break; case ‘green’: return new greenBall(); break; case ‘black’: return new blackBall(); break; case ‘blue’: return new blueBall(); break; }}通过一个工厂产生多个同类的,或者有相同属性但是也存在差异的产品。function createBook(name,time,type) { var t = new Object(); t.name = name; t.time = time; t.type = type; return t;}function createBall(type,text) { var t = new Object(); t.content = text; t.show = function () { // do something } switch (type) { case ‘red’: // different Part break; case ‘green’: // different Part break; case ‘black’: // different Part break; case ‘blue’: // different Part break; }}和类的区别:类是将初始的东西给你,然后你自己去对相应的需求进行添加实例方法。而工厂是根据你需要的方法直接生成好给你。好处,如果有大量相同的含有特殊功能的实例存在,可以通过简单工厂增加复用性。核心:根据情况的不同选择不同的情况进行处理,像是一个封装型的’if else’。2、工厂方法模式工厂方法模式:又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式。它属于类创建型模式。通过对产品类的抽象使其创建业务,只要负责用于创建多类的实例。将实际创建对象的工作推迟到了子类当中。// 类的安全模式var Factory = function (type, content) { if(this instanceof Factory){ return new thistype; }else{ return new Factory(type, content); }};// 创建不同类型基类的实现Factory.prototype={ Java:function (content) { this.content = content; }, PHP:function (content) { this.content = content; }, Python:function (content) { this.content = content; }, JavaScript:function (content) { this.content = content; },}3、抽象工厂模式通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类产品的实例。用于产生类簇。创建一个类,类里面拥有许多抽象的类,抽象的类定义了同类的类的结构。在使用的时候将抽象的类进行继承。/** * 实现subType类对工厂类中的superType类型的抽象类的继承 * @param subType 要继承的类 * @param superType 工厂类中的抽象类type */const VehicleFactory = function(subType, superType) { if (typeof VehicleFactory[superType] === ‘function’) { function F() { this.type = ‘车辆’ } F.prototype = new VehicleFactorysuperType subType.constructor = subType subType.prototype = new F() // 因为子类subType不仅需要继承superType对应的类的原型方法,还要继承其对象属性 } else throw new Error(‘不存在该抽象类’)}VehicleFactory.Car = function() { this.type = ‘car’}VehicleFactory.Car.prototype = { getPrice: function() { return new Error(‘抽象方法不可使用’) }, getSpeed: function() { return new Error(‘抽象方法不可使用’) }}const BMW = function(price, speed) { this.price = price this.speed = speed}VehicleFactory(BMW, ‘Car’) // 继承Car抽象类BMW.prototype.getPrice = function() { // 覆写getPrice方法 console.log(BWM price is ${this.price})}BMW.prototype.getSpeed = function() { console.log(BWM speed is ${this.speed})}const baomai5 = new BMW(30, 99)// baomai5.getPrice() // BWM price is 30// baomai5 instanceof VehicleFactory.Car // true4、建造者模式将一个复杂对象的构建层与其表示层相互分离,同样的构造过程可采用不同的表示。关注产生过程,将对象的创建分为模块化创建,自定义度变高。建造一个电脑:// 构建基本主体const basicComputer = function () {}basicComputer.prototype = { // 自定义的一些原型方法}// 构建CPU模块const cpu = function (type) { this.type = type;}cpu.prototype = { // 自定义的一些原型方法}// 构建显卡模块const graphicsCard = function (type) { this.type = type;}graphicsCard.prototype = { // 自定义的一些原型方法}// 构建屏幕模块const screen = function (type) { this.type = type;}screen.prototype = { // 自定义的一些原型方法}const computer = function () { const t = new basicComputer(); t.cpu = new cpu(); t.graphicsCard = new graphicsCard(); t.screen = new screen(); return t;}5、原型模式用原型实例指向创建对象的类,使用与创建新的对象的类共享原型对象的类型以及方法。基于继承,将复杂的放置在函数中,简单的共同的放置到一个构造函数中。在使用的时候可以对原型进行拓展。代码与继承类似,但是核心就是将简单的共有的放置到构造函数中,与类的思想类似。6、单例模式只允许实例化一次的类。在使用的时候可以用于创建代码库,创建命名空间。单例模式实现代码库,产生命名空间,一次只能实例化一个。// 一个命名空间const A = { fun_1: { fun_1_1:function () { // do something }, }, fun_2: { // do something }, fun_3:function () { // do something }}// 空间类可为一个代码块,也可以为更多一层次的代码库(命名空间) ...

January 16, 2019 · 2 min · jiezi

创建型模式:工厂方法

个人博客原文创建型模式:工厂方法简介姓名:工厂方法英文名:Factory method Pattern价值观:扩展是我的专属个人介绍:Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses. (定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)(来自《设计模式之禅》)你要的故事还记得上一篇 单例模式 中的故事么?小明开着汽车去旅游、去学校、去聚会。这一次还是延续小明的故事,一个故事能讲 2 个设计模式,不容易呀。。。(每次想故事都想破脑袋,每一篇文章至少有 3 个故事从脑子里闪过,但最终留下的只有一个适合,为了就是能比较清晰简单的说明设计模式中的关键要点。)简单工厂小明家里以前不算很富裕,但是还是有一个不错的车库,什么汽车、摩托车、自行车啥的都放在这个车库里。小明每次要出去,都会到车库里面挑合适的车出发。比如,小明最近期末考试了,骑摩托车去学校考试,考完试之后,小明就准备去旅游,这次决定自驾游,开着自己家的小汽车去。这个场景我们用代码描述下。public class SimpleFactoryTest { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); // 小明骑摩托车去学校 IVehicle motorcycle = GarageFactory.getVehicle(“motorcycle”); xiaoMing.goToSchool(motorcycle); // 小明开汽车去旅游 IVehicle car = GarageFactory.getVehicle(“car”); xiaoMing.travel(car); }}/** * 车库 /class GarageFactory { public static IVehicle getVehicle(String type) { if (“car”.equals(type)) { return new Car(); } else if (“motorcycle”.equals(type)) { return new Motorcycle(); } throw new IllegalArgumentException(“请输入车类型”); }}/* * 交通工具 /interface IVehicle { void run();}/* * 汽车 /class Car implements IVehicle { @Override public void run() { System.out.println(“开汽车去。。。。”); }}/* * 摩托车 /class Motorcycle implements IVehicle { @Override public void run() { System.out.println(“骑摩托车去。。。。”); }}class XiaoMing { public void goToSchool(IVehicle vehicle) { System.out.println(“小明去学校”); vehicle.run(); } public void travel(IVehicle vehicle) { System.out.println(“小明去旅游”); vehicle.run(); }}上面代码看懂了么? 小明家里有一个车库 GarageFactory,里面放着汽车 Car 和摩托车 Motorcycle,小明要出去的时候,就到车库选择车,通过传递参数给 GarageFactory.getVehicle(),指明要什么车,然后小明就骑着车出发了。这个代码真正的术语叫:简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式。它是工厂方法中的一个实现方式,从字面理解就可以知道,它是最简单的工厂方法实现方式。它有一点点小缺陷,就是扩展性不够好,在上面代码中,小明只能骑摩托车或者开汽车,如果小明要骑单车出去呢?势必得在 GarageFactory 中添加 if 是自行车的逻辑。这违反了哪条规则了?是不是那个允许扩展,拒绝修改的开闭原则?不是说简单工厂这种实现方式不好,而是扩展性不够,在平时的开发中,简单工厂模式也用得不少。在这个小明家里车不多的情况下,用一个车库也是合适的。工厂方法小明老爸近几年赚了不少,车迷的两父子一直买车,家里的车越来越多,这时候,他们决定多建几个车库,按车类型放置。比如,有一个汽车库,一个摩托车库。这时候小明要开汽车就去汽车库,要骑摩托车就去摩托车库。代码实现如下。public class FactoryMethodTest { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); // 小明骑摩托车去学校 VehicleGarage motorcycleGarage = new MotorcycleGarage(); IVehicle motorcycle = motorcycleGarage.getVehicle(); xiaoMing.goToSchool(motorcycle); // 小明开汽车去旅游 VehicleGarage carGarage = new CarGarage(); IVehicle car = carGarage.getVehicle(); xiaoMing.travel(car); }}interface VehicleGarage { IVehicle getVehicle();}/* * 汽车车库 /class CarGarage implements VehicleGarage { @Override public IVehicle getVehicle() { return new Car(); }}/* * 摩托车车库 */class MotorcycleGarage implements VehicleGarage { @Override public IVehicle getVehicle() { return new Motorcycle(); }}上面代码重用了简单工厂实现方式的交通接口以及摩托车和汽车的实现类。代码中有 2 个车库,一个是汽车车库 CarGarage,一个是摩托车库 MotorcycleGarage。如果小明要骑自行车,只需要建一个自行车车库,完全不用去修改汽车车库或者摩托车车库,就非常符合开闭原则,扩展性大大的提高。总结工厂方法模式可以说在你能想到的开源框架源码中必定会使用的一个设计模式,因为开源框架很重要一点就是要有扩展性,而工厂方法模式恰恰具有可扩展性。弄懂了工厂方法模式,以后看开源代码就很得心应手啦。参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》代码链接:Factory method Pattern希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号 LieBrother,第一时间获取文章推送阅读,也可以一起交流,交个朋友。公众号之设计模式系列文章 ...

January 16, 2019 · 2 min · jiezi

设计模式-观察者模式 发布/订阅模式

设计模式-观察者模式 发布/订阅模式代码观察者接口public interface IHanFeiZi{ // 当吃早饭时 public void havBreakFast(); // 进行娱乐活动时 public void haveFun();}具体的被观察者public class HanFeiZi implements IHanFeiZi{ `// 根据是否在吃饭,作为监控的标准 private boolean isHavingBreakfast = false; // 判断是否在娱乐 private boolean isHavingFun = false; // 当吃饭的时候 public void haveBreakfast(){ System.out.println(“吃饭了”); this.isHavingBreakfast = true; } // 当开始娱乐的时候 public void haveFun(){ System.out.println(“开始娱乐”); this.isHavingFun = true; } // 下方位get/set省去}观察者public interface ILiSi{ // 发行有人有动静,进行行动 public void update(String context);}public class LiSi implements ILiSi{ // 首先他为一个观察者 public void update(String str){ System.out.println(“观察到某人活动,进行汇报”); this.reportToQinShiHuang(str); // 调用汇报方法 System.out.println(“汇报完毕”); } // 进行汇报、 private void reportToQinShiHuang(String reportContext){ System.out.println(“进行汇报”); }}最后定义中间class Spy extends Thread{ private HanFeiZi hanFeiZi; private LiSi liSi; private String type; // 通过构造函数注入,显示将要监控谁 public Spy(HanFeiZi _hanFeiZi, LiSi _liSi, String _type){ this.hanFeiZi = _hanFeiZi; this.liSi = _liSi; this.type = _type; } @Override public void run(){ while(true){ if(this.type.equals(“breakfast”)){ // 监控是否吃早餐 // 如果在吃饭,则通知 if(this.hanFeiZi.isHavingBreakfast()){ this.liSi.update(“在吃饭”); // 重置状态,继续监控 this.hanFeiZi.setHavingBreakfast(false); } }else{ // 判断是否在娱乐 if(this.hanFeiZi.isHavingFun()){ this.liSi.update(“在娱乐”); this.hanFeiZi.setHavingFun(false); } } } }}场景类public class Client{ public static void main(String[] args) throws interruptedException { // 定义两个人 LiSi liSi = new LiSi(); HanFeiZi hanFeiZi = new HanFeiZi(); // 观察早餐 Spy watchBreakfast = new Spy(hanFeiZi, lisi, “breakfast”); watchBreak.start(); // 启动线程、 // 继续查看都干什么 Thread.sleep(1000); hanFeiZi.haveBreakfast(); // 开始吃饭 // 如果娱乐 Thread.sleep(1000); hanFeiZi.haveFun(); // 查看情况 }}修改由于上面使用了一个死循环,会导致出现问题。并且由于多线程的缘故,会导致数据的污染问题,根本无法使用。修改如下public class HanFeiZi implements IHanFeiZi{ // 声明李 private ILiSi lisi = new LiSi(); // 当吃饭的时候 public void hanveBreakfast(){ System.out.println(“吃饭开始”); this.liSi.update(“吃饭了”); } // 当娱乐的时候 public void haveFun(){ System.out.println(“开始娱乐”); this.liSi.update(“在娱乐”); }}最后书写场景类public class Client{ public static void main(String[] args){ // 定义出韩 HanFeiZi hanFeiZi = new HanFeiZi(); // 继续查看韩在干什么 hanFeiZi.haveBreakfast(); // 当其娱乐 hanFeiZi.haveFun(); }}继续改进如果这样聚合,如果有一堆人需要监控被观察者,这样不行,不能一个监控一个,如果此时要修改其他的监控内容,那么都要修改,违反开闭原则开闭原则 对扩展开放,对修改关闭那么,将被观察者的自身活动定义一个接口,被观察者定义接口,在统一的类HanFeiZi中实现该接口,对于观察者来说,定义一个观察者的接口,然后多名观察者分别实现该类被观察者public interface Observable { // 增加一个观察者 public void addObserver(Observer observer); // 删除一个观察者 public void deleteObserver(Observer observer); // 当发生动作的时候,通知观察者 public void notifyObservers(String context);}上方是一个统一的观察者接口,所有的观察者都能实现这个接口。继续查看被观察者public class HanFeiZi implements Observalbe, IHanFeiZi{ // 定义数组,用于保存观察者 private ArrayList observerList = new ArrayList(); // 增加观察者 public void addObserver(Observer observer){ this.observerList.add(observer); } // 删除观察者 public void deleteObserver(Observer observer){ this.observerList.remove(observer); } // 通知所有的观察者 public void notifyObservers(String context){ for(Observer observer:observerList){ observer.update(context); } } // 当要吃饭了 public void haveBreakfast(){ System.out.println(“吃饭”); // 通知观察者 this.notifyObservers(“在吃饭”); } // 开始娱乐 public void haveFun(){ System.out.println(“开始娱乐”); this.notifyObservers(“在娱乐”); }}// 书写观察者接口public interface Observer{ // 发现动静,进行行动 poublic void update(String context);}// 书写三个观察者,只需要实现观察者的接口即可public class LiSi implements Observer{ // 首先要为观察者 public void update(String str){ System.out.println(“开始活动了”); // 汇报 this.reportToQinShiHuang(str); System.out.println(“汇报完毕”); } // 汇报 private void reportToQinShiHuang(String reportContext){ System.out.println(“进行汇报”); }}同理,需要观察者直接实现接口即可。场景类public class Client{ public static void main(String[] args){ // 定义三个观察者 Observer liSi = new LiSi(); Observer wangSi = new WangSi(); Observer liuSi = new LiuSi(); // 定义被观察的 HanFeiZi hanFeiZi = new HanFeiZi(); // 当三个人观察韩的时候 hanFeiZi.addObserver(liSi); hanFeiZi.addObserver(wangSi); hanFeiZi.addObserver(liuSi); // 然后查看在干什么 hanFeiZi.haveBreakfast(); }}这样就完成了观察者模式,当被观察的发生改变的时候,通知被通过构造方法注入的观察者,此观察者保存在动态数组中,然后当被观察者发生改变的时候,只需要使用循环通知保存在数组中的观察者即可。这里有一个接口,如果有多个观察者,可以定义一个接口,只需要实现这个即可即可,声明一个接口,然后使用不同的实例,对接口进行实例化。即可。订阅/发布模型观察者模式同时也叫发布订阅模型,即,消息驱动,消息发布着发布一个消息,然后通过容器实现对订阅者的进行发布,达到通知订阅者的目的,并且订阅者动态的保存在数组中。其他博客 www.iming.info公众号 ...

January 16, 2019 · 2 min · jiezi

模板方法模式

模板方法模式定义定义了一个算法的骨架。并允许子类为一个或多个步骤提供实现。定义补充模板方法是的子类可以在不改变算法结构的情况下,重新定义算法的某些步骤类型行为型适用场景一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码的重复。优点提高复用性,将公共代码放在父类中实现提高了复用性提高拓展性,通过增加子类来拓展新的行为。缺点继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都要修改。下面开始写代码,假设一个场景就是我们要制作一套课程,这些课程可能包含一定包含视频ppt,不一定包含文章。public abstract class ACourse { protected final void makeCourse(){ this.makePPT(); this.makeVideo(); if(needWriteArticle()){ this.writeArticle(); } this.packageCourse(); } final void makePPT(){ System.out.println(“制作PPT”); } final void makeVideo(){ System.out.println(“制作视频”); } final void writeArticle(){ System.out.println(“编写文章”); } //钩子方法 protected boolean needWriteArticle(){ return false; } abstract void packageCourse();}这里定义了一个抽象的课程方法。对于那些一定有的内容我们生命成final的,写文章这个方法我们也声明成final,但是我们定义了一个钩子方法,这个钩子方法子类可以重写,对于需要写文章的方法我们就返回true,不需要的我们就返回false。makeCourse方法中定义了执行流程,在最后我们调用了this.packageCourse方法,这个调用其实是在调用子类方法,因为我们这个是交由子类来执行。通过向上转型来调用父类方法,然后this指代是当前对象也就是子类对象,不太明白的话等会儿看接下来的代码就知道了。这里判断了钩子函数的结果来决定是否执行写文章这个方法。public class DesignPatternCourse extends ACourse { @Override void packageCourse() { System.out.println(“提供课程Java源代码”); } @Override protected boolean needWriteArticle() { return true; }}设计模式课程实现类,这里重写了钩子函数,将其返回值设置成true。这个提供源代码就算是子类自己的实现。public class FECourse extends ACourse { private boolean needWriteArticleFlag = false; @Override void packageCourse() { System.out.println(“提供课程的前端代码”); System.out.println(“提供课程的图片等多媒体素材”); } public FECourse(boolean needWriteArticleFlag) { this.needWriteArticleFlag = needWriteArticleFlag; } @Override protected boolean needWriteArticle() { return this.needWriteArticleFlag; }}这里是前端课程,因为前端是一个大的区域,前端可以分为vue,react等等,所以我们把这个钩子函数也开放出来,只是给一个默认值让应用层自己实现。public class TemplateMethodTest { public static void main(String[] args) { System.out.println(“后端设计模式课程start—”); ACourse designPatternCourse = new DesignPatternCourse(); designPatternCourse.makeCourse(); System.out.println(“后端设计模式课程end—”); System.out.println(“前端课程start—”); ACourse feCourse = new FECourse(false); feCourse.makeCourse(); System.out.println(“前端课程end—”); }}测试方法,这就讲完了。 ...

January 15, 2019 · 1 min · jiezi

创建型模式:单例模式

个人博客原文:创建型模式:单例模式简介姓名:单例模式英文名:Singleton Pattern价值观:我的生活我主宰(只允许自己实例化,不愿意被其他对象实例化)个人介绍:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)(来自《设计模式之禅》)这里的关注点有 3 个,分别是:只有一个实例自行实例化(也就是主动实例化)向整个系统提供这个实例你要的故事我们脑洞大开来用一个故事讲解一番。小明家里有一辆小汽车,具体什么牌子就不知道了,咱也不关注,反正他家里就这么一辆车,小明比较懒,只要一出门都会开车,例如去旅游、去学校、去聚会都会开车去。下面模拟小明出去的场景。class Car { public void run() { System.out.println(“走。。。。”); }}class XiaoMing { public Car travel() { System.out.println(“小明去旅游”); Car car = new Car(); car.run(); return car; } public Car goToSchool() { System.out.println(“小明去学校”); Car car = new Car(); car.run(); return car; } public Car getTogether() { System.out.println(“小明参加聚会”); Car car = new Car(); car.run(); return car; }}public class SingletonErrorTest { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); Car car1 = xiaoMing.travel(); Car car2 = xiaoMing.goToSchool(); Car car3 = xiaoMing.getTogether(); }}上面小汽车只有一个方法,就是走。小明去旅游、去学校、参加聚会都开着他唯一的一辆汽车车去。是不是有人有疑问?为什么每个方法都返回 Car 对象?其实只是想在下面做一次检查,检查小明去旅游、去学校和参加聚会的车是不是同一辆。下面是检查代码:System.out.println(“car1 == car2 ? " + (car1 == car2));System.out.println(“car2 == car3 ? " + (car2 == car3));最终结果是啥?很明显是 2 个 false。小明去旅游、去学校和参加聚会的车都不相同,小明不是只有 1 辆车?关键在于 Car car = new Car(); 这一句代码,其实这一句是创建一辆车,每次都重新创建一辆。那应该怎么实现小明只有一辆车呢?这时候就引入了单例模式。上面我们说到了单例模式需要具备的 3 个点:只有 1 个实例,很显然,上面的代码不止 1 个实例,而是有 3 个 Car 实例;自行实例化,Car 本身没有主动实例化,而是在小明需要用到的时候才实例化;向整个系统提供这个实例,因为 Car 没有主动实例化,所以它没法向外部暴露提供自己出来。我们的代码完全不符合单例模式的要求。我们要通过修改,使之符合单例模式的 3 个要点。首先需要实现的是第 2 点,把 Car 实例化从小明转为 Car 本身,如下代码class Car1{ private static Car1 car1 = new Car1(); private Car1() { } public void run(){ System.out.println(“走。。。。”); }}上面代码使用 private 修饰构造方法,使得 Car1 不能被其他使用方实例化,通过 Car1 car1 = new Car1(); 主动实例化自己。接下来再实现第 3 点,向整个系统暴露这个实例,也就是暴露它自己。每个使用方都调用 Car1.getInstance() 方法来获取实例。class Car1{ private static Car1 car1 = new Car1(); public static Car1 getInstance() { return car1; } private Car1() { } public void run(){ System.out.println(“走。。。。”); }}上面代码就实现了单例模式的 2 和 3 要点,第 1 要点要怎么实现呢?告诉你,不用实现,只要满足了 2 和 3 要点就可以,第 1 要点是用来检验是否是单例模式的好思路。我们检验一下class Car1{ private static Car1 car1 = new Car1(); public static Car1 getInstance() { return car1; } private Car1() { } public void run(){ System.out.println(“走。。。。”); }}class XiaoMing1 { public Car1 travel() { System.out.println(“小明去旅游”); Car1 car = Car1.getInstance(); car.run(); return car; } public Car1 goToSchool() { System.out.println(“小明去学校”); Car1 car = Car1.getInstance(); car.run(); return car; } public Car1 getTogether() { System.out.println(“小明参加聚会”); Car1 car = Car1.getInstance(); car.run(); return car; }}public class SingletonRightHungryTest { public static void main(String[] args) { XiaoMing1 xiaoMing1 = new XiaoMing1(); Car1 car1 = xiaoMing1.travel(); Car1 car2 = xiaoMing1.goToSchool(); Car1 car3 = xiaoMing1.getTogether(); System.out.println(“car1 == car2 ? " + (car1 == car2)); System.out.println(“car2 == car3 ? " + (car2 == car3)); }}上面代码最后两行打印出来的结果是啥?是我们想要的:2 个 true。说明小明这几次外出开的车都是同一辆。这是最简单的单例模式的实现方式,我们经常称作饿汉式单例模式。为什么起这么古怪的名字呢?其实和对应的懒汉式单例模式有关,这是 2 个实现方式的差别,饿汉式单例模式实现方式在类加载到内存的时候,就创建好对象了,而懒汉式则是在第一次使用的时候才创建对象,也就是把创建对象的时机从加载延迟到第一次使用,所以才有懒饿之分。下面我们来看怎么实现懒汉式单例模式。先描述一下场景:小明还没有汽车,他也不知道什么时候要买汽车,突然某一天,他想去旅游,觉得是时候买辆车了,然后他就买车去旅游了,旅游回来又开车去学校和参加聚会。class Car2{ private static Car2 car2; public static synchronized Car2 getInstance() { if (null == car2) { System.out.println(“买车啦。。。”); car2 = new Car2(); } return car2; } private Car2() { } public void run(){ System.out.println(“走。。。。”); }}class XiaoMing2{ public Car2 travel() { System.out.println(“小明去旅游”); Car2 car = Car2.getInstance(); car.run(); return car; } public Car2 goToSchool() { System.out.println(“小明去学校”); Car2 car = Car2.getInstance(); car.run(); return car; } public Car2 getTogether() { System.out.println(“小明参加聚会”); Car2 car = Car2.getInstance(); car.run(); return car; }}public class SingletonRightLazyTest { public static void main(String[] args) { XiaoMing2 xiaoMing2 = new XiaoMing2(); Car2 car1 = xiaoMing2.travel(); Car2 car2 = xiaoMing2.goToSchool(); Car2 car3 = xiaoMing2.getTogether(); System.out.println(“car1 == car2 ? " + (car1 == car2)); System.out.println(“car2 == car3 ? " + (car2 == car3)); }}小明去旅游买车啦。。。走。。。。小明去学校走。。。。小明参加聚会走。。。。car1 == car2 ? truecar2 == car3 ? true上面附带了打印出来的结果,小明要去旅游的时候,才去买车。这就是懒汉式单例模式的实现方式。要注意懒汉式单例模式有个很关键的一点就是 getInstance() 方法带上了 synchronized,这个是为什么呢?首先得了解关键字 synchronized 的作用是什么:用于修饰执行方法同步,也就是说多线程并发的情况下,在一个时间点,只允许一个线程执行这个方法。不加上这个会有什么结果?在多线程并发情况下,如果有 2 个线程同时执行到 if(null == car2),那么都判断为 true,这时 2 个线程都会执行 car2 = new Car2(),这样子就不是单例了。总结单例模式可以说是设计模式中最简单的一个,也是在工作中很多场景下经常用到的,比如:项目的配置文件加载、各种工具类等等。我们对于单例模式最重要的一点就是要考虑多线程并发,没有考虑这点就容易引发单例对象不单例的情况。而单例给我们带来最大的好处就是节约内存。上面实现的两种方法是单例模式中最最最简单的 2 种实现,相信也是用得最多的实现方式。网上有不少网友分享了单例模式的很多种实现方法,大家也可以去了解,在了解之前务必已经搞懂文中这 2 种最简单的实现方式,不然会头晕的。参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。公众号之设计模式系列文章 ...

January 15, 2019 · 3 min · jiezi