《Head First Design Patterns》:第5章,单例(Singleton)模式
方案一
1 | public class Singleton { |
缺点:无法应对多线程的场景,即使将getInstance()
加上synchronized
关键字也会带来性能方面的问题。
方案二:饿汉模式
1 | public class Singleton { |
方案三:双重检查加锁+饿汉模式
1 | public class Singleton { |
1 | public class Singleton { |
缺点:无法应对多线程的场景,即使将getInstance()
加上synchronized
关键字也会带来性能方面的问题。
1 | public class Singleton { |
1 | public class Singleton { |
披萨店
披萨店提供预定
功能,可以提供多种类型(参数type
)披萨的预定;已知的是:a)披萨店会时常调整所提供的披萨种类,b)披萨制作过程(包含:准备、烘焙、切片、包装)不会经常变化;代码如下:
1 | public class PizzaStore { |
采用这样的设计,每当披萨店有新品披萨上架、或下架某类披萨时就需要对该代码进行修改,如下图所示:
使用简单工厂,对orderPizza(String type)
方法中变化的部分进行封装,使orderPizza(String type)
方法对“修改关闭”,于是我们将创建对象的代码从orderPizza(String type)
方法中抽象出来,如下;这样orderPizza(String type)
方法只需要从工厂中得到比萨对象,然后对它进行一系列的制作过程。
1 | public class SimplePizzaFactory { |
1 | public class PizzaStore { |
现在考虑开展披萨加盟店,经营者为保障加盟店营运的质量,希望这些加盟店尽量使用之前披萨店的代码(即,PizzaStore
);又已知的是,每个地区的加盟店可能想要提供不同风味的比萨。按照简单工厂的方式,一个可能的设计可能会是:每家加盟店都有自己的工厂来创建具体的披萨对象,如下:
1 | public class NYPizzaFactory { |
但是调研中发现,并不是所有的加盟者都希望完全按照PizzaStore
的流程来制作披萨,往往他们都是一些有着丰富经验的厨师,他们想要加入自己的一些过程设计。这样的话,PizzaStore
就不能满足需要了,因为它把制作披萨的过程牢牢地绑定到了其内部。
我们a)把PizzaStore
类声明为抽象类,b)把简单工厂中的方法拿回到PizzaStore
类中,并将其声明为抽象方法,c)为每个区域的店创建一个PizzaStore
类的子类。这样,可以让比萨的制作活动仍然局限在PizzaStore
类,而同时又让这些加盟店能够自由地制作该区域风味的披萨。我们称createPizza()
为工厂方法,如下:
1 | public abstract class PizzaStore { |
纽约州的披萨店:
1 | public class NYPizzaStore extends PizzaStore { |
如此一来,每个地区的加盟店类型都继承PizzaStore
类,并各自决定如何制作自己的披萨(即,定义各自的createPizza()
方法);并且每个PizzaStore
的具体子类都有自己的比萨变体。这样就做到了所有加盟店仍然使用PizzaStore框架,并可以使用已经非常不错的订单系统(即,orderPizza()
方法)。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
每个具体工厂都可以生产一整组产品。
为了确保加盟店使用高质量的原料,我们打算建造原料工厂,并将原料运送到各家加盟店;并且,针对不同区域的加盟店,原料工厂能提供只适合该区域加盟店的原料。
已知所有披萨所需要的原料种类都是相同的(面团、酱料、芝士、海鲜佐料),这些原料的制作方式根据区域的不同而有差异。
工厂接口PizzaIngredientFactory
中定义所有原料创建的方法:
1 | public interface PizzaIngredientFactory { |
为每个区域建造一个工厂,使用该区域所特有的一组原料供工厂使用:
1 | public class NYPizzaIngredientFactory implements PizzaIngredientFactory { |
1 | public abstract class Pizza { |
奶酪披萨使用工厂接口PizzaIngredientFactory
来准备原料:
1 | public class CheesePizza extends Pizza { |
具体的披萨店知道从哪个工厂进行原料采购:
1 | public class NYPizzaStore extends PizzaStore { |
工厂方法 | 抽象工厂 |
---|---|
1.不需要另外定义接口,只需要定义抽象方法; 2.使用继承,把对象的创建委托给子类 |
1.抽象工厂的任务是定义一个负责创建一组产品的接口,接口中的每个方法都负责创建一个具体产品; 2.使用对象组合,对象的创建被实现在工厂接口所暴露出来的方法中; |
装饰器模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
如下:
ConcreteComponent
是我们将要动态地加上新行为的对象,它扩展自Component
;Decorator
都有一个(包装一个)组件对象,也就是说,装饰者有一个实例变量保存某个Component
是的引用;星巴兹咖啡店的订单系统
星巴兹咖啡店扩展迅速,他们准备更新订单系统,以合乎他们的饮料供应要求。类设计原来是这样的:a)饮料类Beverage
是一个抽象类,店内所有提供的饮料都要继承此类;b)cost()
方法是抽象的,子类必须定义自己的实现,以实现对每种饮料计算价格;c)购买咖啡时,也可以加入各种调料,例如:蒸奶、豆浆、摩卡或覆盖奶泡;星巴兹会根据所加入的调料收取不同的费用。
采用这样的设计,他们的类将会爆炸:
这里采用的做法是:以饮料为主体,在运行时以调料来“装饰”饮料。比如,如果顾客想要摩卡和奶泡深焙咖啡,那么需要这样做:
DarkRoast
对象;Mocha
对象装饰它;Whip
对象再进行装饰它;cost()
方法,并依赖委托将调料的价钱加上去;1 | public abstract class Beverage { |
实现一些饮料,我们先实现浓缩咖啡Espresso
和综合咖啡HouseBlend
:
1 | public class Espresso extends Beverage { |
调料抽象类:
1 | public abstract class CondimentDecorator extends Beverage { |
实现摩卡口味的咖啡:a)getDescription()
中加上摩卡的描述,b)cost()
如中加上了摩卡的费用,如下:
1 | public class Mocha extends CondimentDecorator { |
我们来使用一下新的订单系统:
1 | public class StarbuzzCoffee { |
InputStream
是抽象组件;FileInputStream
、StringBufferInputStream
、ByteArrayInputStream
是可以被装饰者包起来的具体组件,还有少数类没有被列出,如ObjectInputStream
;PushbackInputStream
、BufferedInputStream
、DataInputStream
、InflatorInputStream
、ZipInputStream
是具体的装饰者;实现输入流中所有大写字符转成小写的例子:
1 | public class LowerCaseInputStream extends FilterInputStream { |
观察者模式定义了对象之间的一对多依赖,这样,当一个对象改变状态的时候,它的所有依赖者都会收到通知并自动更新。
Weather-O—Rama的Internet气象观测站
该气象站建立在该公司的专利WeatherData
类上,WeatherData
类负责跟物理气象站联系,以获得最新的天气状况(温度、湿度、气压),然后实时更新各种显示设备。目前的显示设备有三种:a)显示目前状况(温度、湿度、气压)的设备,b)显示气象统计的设备,c)显示天气预报的设备;WeatherData
类还需要具备方便未来有其它显示设备以插拔的方式接入的功能。
已有WeatherData
类,其中
getTemperature()
、getHumidity()
、getPressure()
是气象站的开发人员已经实现好的方法;measurementsChanged()
,该方法一旦气象测量有更新就会被调用;1 | public class WeatherData { |
相关接口:
1 | public interface Subject { |
实现WeatherData
:
1 | public class WeatherData implements Subject { |
实现显示设备:
1 | public class CurrentConditionsDisplay implements Observer, DisplayElement { |
观测站运行:
1 | public class WeatherStation { |
注意点:
java.util.Observable
是类;java.util.Observable
中的setChanged()
方法;策略模式定义了算法簇,封装每一个算法,使得这些算法可以互相替换;该模式让算法的变化独立于使用算法的客户。
以下面的例子为例,Flyable
定义了一个飞的算法簇,FlyWithWings
和FlyNoWay
这两个飞的具体算法可以互相替换。
鸭子池塘模拟游戏SimUDuck
所有鸭子都会呱呱叫quack()
,也会游泳swin()
,所以抽象类Duck
负责实现quack()
和swin()
;而每种鸭子的外观是不同的,所以抽象类Duck
的display()
方法是抽象的。
现在,该游戏需要升级,考虑给鸭子加上飞fly()
的行为(注意,并不是所有的鸭子都会飞)。假如,按照我们前面设计,直接将fly()
方法在抽象类Duck
中进行实现,那么就会造成,所有子类具有飞fly()
的行为,而假如我们有一只橡皮鸭子(它是不能飞翔的),那么它现在也会飞了:
那么,继续在橡皮鸭子RubberDuck
中重写fly()
方法为“什么都不做”,是否可行呢?这样固然能解决橡皮鸭子不会飞的问题,但是假如我们又添加了一个诱饵鸭DecoyDuck
,还需要再次添加对fly()
方法的重写:
那么用接口实现如何?因为并不是所有鸭子都会飞fly()
和叫quack()
,所以把这两个行为放到单独的接口中。这样的话,重复代码会变多,因为绿头鸭和红头鸭都需要实现fly()
和quack()
,造成代码无法复用:
我们知道Duck
类内的fly()
和quack()
会随着鸭子的不同而改变。我们将这两个行为从Duck
类中取出来,建立一套鸭子的行为类来代表每个行为。
采用这样的设计,可以让飞行fly()
和呱呱叫quack()
的动作被其它的对象复用,因为这些行为已经和鸭子类无关了。同时,我们可以新增一些行为,而不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
1 | public abstract class Duck { |
1 | public interface FlyBehavior { |
1 | public interface QuackBehavior { |
(待续)
1 | // create a TableEnvironment for specific planner batch or streaming |
1 | tableEnvironment |
1 |
1 | tableEnvironment.executeSql("CREATE [TEMPORARY] TABLE MyTable (...) WITH (...)") |
1 |
1 | // get a TableEnvironment |
1 | // compute revenue for all customers from France |
1 | // compute revenue for all customers from France |
1 | // create an output Table |
1 | // compute revenue for all customers from France and emit to "RevenueFrance" |