Object-Oriented Programming (Java)
Design Pattern Factory 工厂模式 1 Decorator 装饰者模式 2 Observer 观察者模式 3
New的危害 也就是说,一旦有变化和扩展,这段代码就要被重新打开进行修改。使系统维护变得困难。面向对象一个重要原则:对扩展开放,对修改封闭。 Duck duck=new MallardDuck(); 要使用接口,让代码更有弹性 但是还是得建立具体类的实例 当有一群相关的具体类时,通常会写出这样的代码: Duck duck; if(picnic) { duck =new MallardDuck(); } else if(hunting) { duck =new DecoyDuck(); } else if(inBathTub) { duck =new RubberDuck(); } 有一大堆不同的鸭子类,必须要等到运行时,才知道例化哪一个 也就是说,一旦有变化和扩展,这段代码就要被重新打开进行修改。使系统维护变得困难。面向对象一个重要原则:对扩展开放,对修改封闭。
Let’s start from an example 假如你有一个比萨店,身为对象村内最先进的萨店的主人,你的代码可能这么写: Pizza OrderPizza() { Pizza pizza =new Pizza(); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } 为了让系统更有弹性,我们很希望这是一个抽象类或接口。但如果这样,这些类或接口就无法真接实例化。
事情往往很复杂 但是你需要更多的比萨类型: 所以必须要增加一些代码,来“决定”适合的比萨类型,然后再“制造”这个比萨: Pizza orderPizza( ) { Pizza pizza; pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; String type 现在把比萨类型号传入orderPizza()。 if (type.equals("cheese")) { pizza =new CheesePizza(); } else if (type.equals("greek")) { pizza =new GreekPizza(); } else if (type.equals("pepperoni")) { pizza=new PepperoniPizza(); } 根据比萨的类型,我们实例化正确的具体类,然后将其赋值为pizza实例变量。请注意,这里的任何比萨都必须实现pizza接口。 一旦我们有了一个比萨,需要做一些准务(就是擀揉面皮、加上佐料,例如芝士),然后烧烤、切片、装盒!
问题的根源 这是变化的部分。随着时间过去,比萨菜单改变,这里就必须一改再改。 但是压力来自于增加更多的比萨类型 你发现你所有的竞争者都已经在他们的菜单中加入了一些流行风味的比萨:Clam Pizza(蛤蜊比萨)、Veggie Pizza(素食比萨)。很明显,你必须要赶上他们,所以也要把这些风味加进你的菜单中。而最近Greek Pizza(希腊比萨)卖得不好,所以你决定将它从菜单中去掉: Pizza orderPizza(String type) { Pizza pizza; if (type.equals("cheese")) { pizza =new CheesePizza(); } else if (type.equal("greek")) { pizza =new GreekPizza(); } else if (type.equal("pepperoni")) { pizza =new PepperoniPizza(); 这是变化的部分。随着时间过去,比萨菜单改变,这里就必须一改再改。 此代码“没有”对修改封闭。如果比萨店改变它所供应的比萨风味,就得进到这里来修改。 } else if (type.equal(“Clam”)) { pizza =new ClamPizza(); } else if (type.equal(“veggie”)){ pizza =new VeggiePizza(); } 这里是我们不想改变的地方,因为比萨的准备、烘烤、包装、多年来都保持不变,所以这部分代码不会改变,只有发生这些动作的比萨会改变。 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza;
工厂模式出现了 封装创建对象的代码 现在最好将创建对象移到orderPizza()之外,但怎么做呢,这个嘛,要把创建比萨的代码移到另一个对象中,由这个新对象专职创建比萨。 if (type.equals("cheese")) { pizza =new CheesePizza(); } else if (type.equal("greek")) { pizza =new GreekPizza(); } else if (type.equal(“pepperoni”)) { pizza =new PepperoniPizza(); } else if (type.equal("Clam")) { pizza =new ClamPizza(); } else if (type.equal("veggie")) { pizza =new VeggiePizza(); } Pizza orderPizza(String type) { Pizza pizza; 首先,把创建对象的代码 从orderPizza()方法中抽离 ? pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } 然后把这部分的代码搬到另一个对象中,这个新对象只管如何创建比萨,如果任何对象想要创建比萨,找它就对了
解读工厂模式 工厂(factory)处理创建对象的细节。一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者蛤蜊比萨的日子一去不复返了。现在orderPizza()方法只关心从工厂得到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare()、bake()、cut()|、box()来分别进行准备、烘烤、切片、装盒。 工厂模式的目的: Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.
建立一个简单的工厂 先从工厂本身开始。我们要定义一个类,为所有比萨封装创建对象的代码。代码像这样………… SimplePizzaFactory是我们的新类,它只做一件事,帮它的客户创建比萨。 public class SimplePizzaFactory { { if (type.equals("cheese")) { pizza =new CheesePizza(); } else if(type.equals("pepperoni")) { pizza =new PepperoniPizza(); } else if(type.equals("clam")) { pizza =new ClamPIzza(); } else if(type.equals("veggie")) { pizza =new VeggiePizza(); } return pizza; public Pizza createPizza(String type) Pizza pizza =null; 首先,在这个工厂内定义一个createPizza()方法。所有客户用这个方法来实例化新对象。 这是从orderPizza()方法中移过来的代码。这个代码没什么变动,和原本orderPizza()方法中的代码一样,依然是以比萨的类型为参数。
重建我们的披萨店By工厂模式 创建比萨,要做这样的改变: 为PizzaStore加上一个对SimplePizzaFactory的引用。 public class PizzaStore { } public Pizza orderPizza (String type) { Pizza pizza; pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; //这里是其他方法 SimplePizzaFactory factory; public PizzaStore(SimplePizzaFactory factory) { this.factory=factory; PizzaStore的构造器,需要一个工厂作为参数。 orderPizza()方法通过简单传入订单类型来使用工厂创建比萨。请注意,我们把new操作符替换成工厂对象的创建方法,这里不再使用具体实例化。 pizza =factory.createPizza(type); 注意:原先的if-else不见了!
工厂模式的庐山真面目 工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 Document Open() Close() Save() Revert() Application CreateDocument() NewDocument() OpenDocument() Document* doc=CreateDocument(); docs.Add(doc); doc→Open(); MyDocument MyApplication CreateDocument() return new MyDocument 大家可以跟先前的问题对应一下:Application: SimplePizzaFactory Document: PizzaStore 工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
每个子类实现cost()来返回饮料的价钱。 第二个设计模式,从星巴兹咖啡开始 星巴兹是一家扩张速度很快的咖啡店,所以他们打算更新他们的订单系统,以适应新的情况。原先的设计如下: 这个实例变量,由每个子类设置,用来描述饮料。利用getDescription()方法来返回此描述。 Beverage(饮料)是一个抽象类,店内所提供的饮料都必须继承此类。 Beverage description getDescription() cost() //其它有用方法 cost()方法是抽象的,子类必须定义自己的实现。 HouseBlend cost() DarkRoast cost() Decaf cost() Espresso cost() 每个子类实现cost()来返回饮料的价钱。
A huge catastrophe 如果人们买咖啡时要加入一些原料,按原先的设计那就是程序员的catastrophe。 Beverage description getDescription() cost() //其它有用方法 DarkRoast cost() HouseBlend cost() Decaf cost() Espresso cost() 表中省略了许多原料表格。每个cost()方法将计算出咖啡加上订单上各种调料的价钱。
使用继承之后 现在加入子类,每个类代表菜单上的一种饮料: Beverage description milk soy mocha whip getDesription() cost() hasMilk() setMilk() hasSoy() setSoy() hasMocha() setMocha() hasWhip() setWhip() //其他有用的方法…… 超类cost()将计算所有调料的价钱,而子类覆盖过的cost()会扩展超类的功能,把指定的饮料类型的价钱也加进来。 每个cost()方法需要计算该饮料的价线,然后通过调用超类的cost()实现 ,加入调料的价格。 Soy酱;whip DarkRoast cost() Decaf cost() Espress0 cost() HouseBlend cost()
装饰者模式,出来了 认识装饰者模式 好了,我们已经了解利用继承无法完全解决问题,在星巴兹遇到的问题有:类数量爆炸、设计死板,以及基类加入的新功能并不适用于所有的子类。 所以,在这里采用不一样的做法:我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是: 拿一个深焙咖啡(DarkRoast)对象 以摩卡(Mocha)对象装饰它 以奶泡(Whip)对象装饰它 调用cost()访求,并依赖委托(delegate)将调料的价钱加上去。 好了,但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?给一个暗示:把装饰者对象当成“包装者”。让我们看看这是如何工作的………
装饰的过程(1) 别忘了,DarkRoast继承自Beverage,且有一个用来计算饮料价钱的cost()方法。 顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包(wrap)起来。 Mocha对象是一个装饰者,它的类型“反映”了它所装饰的对象(本例中,就是Beverage),所谓的“反映”,指的就是两者类型一致。所以Mocha也有一个cost()方法,通过多态,也可以把Mocha所包裹的任何Beverage当成是Beverage(因为Mocha是Beverage的子类型)。 cost() Mocha DarkRoast
装饰的过程(2) 顾客也想要奶泡(Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包起来。别忘了,DarkRoast继承自Beverage,且有一个cost()方法,用来计算饮料价钱。 cost() Mocha DarkRoast Whip Whip是一个装饰者,所以它也反映了DarkRoast类型,并包括一个cost()方法。 所以,被Mocha和Whip包起来的DarkRoast对象仍然是一个Beverage,仍然可以具有DarkRoast的一切行为,包括调用它的cost()方法。
装饰的过程(3) 现在,该是为顾客算钱的时候了。通过调用最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,然后再加上奶泡的价钱。 ②Whip调用Mocha的cost(). cost() Mocha DarkRoast Whip 首先,调用最外圈装饰者Whip的cost(). ③Mocha调用DarkRoast的cost(). .10 .20 .99 $1.29 ④DarkRoast返回它的价钱$0.99. ⑤Mocha在DarkRoast的结果上,加上自己的价钱$0.20,返回新的价钱$1.19. ⑥Whip在Mocha的返回结果上加上自己 价钱$0.10,然后返回了后结果$1.29.
定义装饰者模式 Component Operation() ConcreteComponent Operation() Decorator ConcreteDecoratorA Operation() addedState ConcreteDecoratorB Operation() AddedBehavior() Decorator::Operation(); AddedBehavior();
星巴兹的代码(1) public abstract class Beverage { Beverage是一个抽象类,有两个方法:getDescription()及cost(). public abstract class Beverage { String description ="Unkown Beverage"; public String getDescription() { return description; } public abstract double cost(); public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); getDescription()已经在此实现了,但是cost()必须在子类中实现。 所有的调料装饰者都必须重新实现getDescription()方法,稍后我们会解释为什么……
星巴兹的代码(2) public class Espresso浓咖啡 extends Beverage { 首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。 public class Espresso浓咖啡 extends Beverage { public Espresso() { description ="Espresso"; } public double cost() { return 1.99; 为了要设置饮料的描述,我们写了一个构造器。记住,description实例变量继承自Beverage. 最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格$1.99返回即可。 浓咖啡
星巴兹的代码(3) public class HouseBlend extends Beverage { public HouseBlend () { description ="House Blend Coffee"; } public double cost() { return .89; 这是另一种饮料,做法和Espresso一样,只是把Espresso名称改为“HouseBlend Coffee”,并返回正确的价钱$0.89.
星巴兹的代码(4) public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage=beverage; } public String getDescription() { return beverage.getDesription() +”, Mocha”; public double cost() { return .20 + beverage.cost(); 要让Mocha能够引用一个Beverage,做法如下:(1)用一个实例变量记录饮料,也就是被装饰者。(2)想办法让被装饰者(饮料)被记录以实例变量中。这里的做法是:把饮料发作构造器的参数,再构造器将些饮料记录在实例变量中。 摩卡是一个装饰者,所以让它扩展自CondimentDecorator.别忘了,CondimentDecorator扩展自Beverage. 我们希望叙述不只是描述饮料(例如“DarkRoast”),而是完整地连调料都描述出来(例如“DarkRoast,Mocha”),所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙述(例如“Mocha”)。 调味品 要计算带Mocha饮料的价钱,首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的价钱,得到最后 结果。
星巴兹的代码(5) public class StarbuzzCoffee { public static void main (String args[]) { Beverage beverage=new Espresso(); System.out.println(beverage.getDescription() + “$” + beverage.cost()); Beverage beverage2=new DarkRoast(); beverage2 =new Mocha(beverage2); beverage2 =new Whip(beverage2); System.out.println(beverage2.getDescription() + “$” + beverage2.cost()); Beverage beverage3 =new HouseBlend(); beverage3 =new Soy(beverage3); beverage3 =new Mocha(beverage3); beverage3 =new Whip(beverage3); System.out.println(beverage3.getDescription() + “$” + beverage3.cost()); } 订一杯Espresso,不需要调料,打印出它的描述与价钱。 制造出一个DarkRoast对象。 用Mocha装饰它;用第二个Mocha装饰它;用Whip装饰它。 最后,再来一杯调料为豆浆、摩卡、奶泡的HouseBlend咖啡。
Java中的装饰者,你发现了没? 1001 1110100 001010 1010111 FileInputStream BufferedInputStream LineNumberInputStream 1001 1110100 001010 1010111 LineNumberInputStream也是一个具体的“装饰者”。它加上了计算行数的能力。 FileInputStream是被装饰的“组件”,Java I/O程序库提供了几个组件,包括了FileInptStream、StringBufferInputStream、ByteArrayInputStream……等,这些类都提供了最基本的字节读取功能。 BufferedInputStream是一个具体的“装饰者”,它加入两种行为:利用缓冲输入来改进性能:用一个readLine()方法(来一次读取一行文本输入数据)来增强 接口。
You can do it!(1) 这是我们的抽象组件组,FilterInputStream是一个抽象装饰者。 InputStream FileInputStream FilterInputStream BylArrayInputStream StringBrWarInputStream PushbackInputStream BufferedInputStream DataInputStream LineNumberInputStream FileINputStream、ByleArrayInputStream和StringBrWarInputStream类是可以被装饰者包起来的具体组件。还有少数类没有显示在这里。例如ObjectInputStream. 最底层为具体装饰者。
You can do it!(2) 首先,扩展FilterInputStream,这是所有InputStream的抽象装饰者。 Let’s write our own in class! public class LowerCaseInputStream extends FiterInputStream { public Lower CaseInputStream(InputStream in) { super(in); } public int read() throws IOException { int c=super.read(); return (c == -1 ? c: Character.toLowerCase((char)c)); public int read(byte[] b,int offset ,int len) throws IOException { int result =super.read(b ,offset ,len); for (int i=offset; i <offset+result;i++) { b[i] =(byte)Character.toLowerCase((char)b[i]); return result; 现在,必须实现两个read()方法,一个针对字节,一个针对字节数组,把每个是大写字母的字节(每个代表一个字符)转成小写。
You can finish it! public class InputTest { public static void main (String [] args) throws IOException { int c; try { InputStream in= new LowerCaseInputStream ( new BufferedInputStream ( new FileInputStream (“test.txt”))); while ((c =in.read()) >0) { System.out.print ((char) c); } in.close(); } catch (IOExcetion e) { e.printStackTrace(); 设置FileInputStream,先用BufferedInputStream装饰它,再用我们崭新的LowerCaseInputStream过滤器装饰它。 只用流来读取字符,一直到文件尾端。
一份气象站的合约引发的故事 工作合约 恭喜贵公司获选为敝公司建立下一代Internet气象观测站!该气象站必须建立在我们专得申请中的WeatherData对象上,由WeatherData对象负责追踪目前的天气状况(温度、温度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject对象获得最新的测量数据时,三种布告板必须实时更新。 而且,这是可以一个可以扩展的气象站,Weather-o-Rama气象站希望一组API,好让其他开发人员可以写出自己 气象布告板,并插入此应用中。我们希望贵公司能提供这样的API。 Weather-o-Rama气象站有很好的商业营运模式:一旦客户上钩,他们使用每个布告板都要付钱。最好的部分就是,为了感谢贵公司建立此系统,我们将以公司 认股权支付你。 我们期待看到你的设计和应用的alpha版本。 真挚的 Johnmy Humcane Weather-o-Rama气象站执行人
项目的结构 “目前状况”是三种显示之一,用户民可以获得气象统计与天气预报。 取得数据 显示 目前 状况 温度:25℃ 湿度:60 气压: ↓ 湿度感应装置 WeatherData对象 温度感应装置 气象站 气压感应装置 显示装置 Weather-0-Rama提供 我们的实现
一种最直接的实现! public class WeatherData { //实例变量声明 public void measurementsChanged() { float temp =getTemperature(); float humidity =getHumidity(); float pressure =getPressure(); currentConditionsDisplay.update(temp ,humidity,pressure); statistiosDisplay.update(temp, humidity,pressure); forecastDisplay.update(temp, humidity, pressure); } //这里是其他WeatherData方法 调用WeatherData的三个getXxx()方法,以取得最近的测量值。这些getXxx()方法已经实现好了。 现在,更新布告板…… 调用每个布告板更新显示,传入最新的测量。
问题又来了,仔细看,用心想! public void measurementsChanged() { float temp =getTemperature(); float humidity =getHumidity(); float pressure =getPressure(); currentConditionsDisplay.update (temp ,humidity,pressure); statistiosDisplay.update(temp, humidity,pressure); forecastDisplay.update(temp, humidity, pressure); } 改变的地方,需要封装起来 至少,这里看起来像是一个统一的接口,布告板的方法名称都是update(),参数都是温度、湿度、气压。 针对具体实现编程,会导致我们以后在增加或删除布告板时必须修改程序。
主角登场By an instance 报社的业务就是出版报纸。 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。 只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。
观察者模式的活动(1) 主题对象 鸭子对象 主题对象 狗对象 猫地象 老鼠对象 鸭子对象过来告诉主题,它想当一个观察者。 鸭子其实想说的是:我对你的数据改变感兴趣,一有变化请通知我 主题对象 鸭子对象 要注册(订阅) 许多观察者 狗对象 鸭子对象 老鼠对象 猫对象 鸭子对象现在已经是正式的观察者了。 鸭子静候通知,等待参与这项伟大的事情。一旦接获通知,就会得到一个整数。 主题对象 许多观察者
观察者模式的活动(2) 主题有了新的数据值! 主题对象 现在鸭子和其他所有观察者都会收到通知:主题已经改变了。 主题对象 狗对象 鸭子对象 老鼠对象 猫对象 主题有了新的数据值! 主题对象 8 8 8 8 现在鸭子和其他所有观察者都会收到通知:主题已经改变了。 8 许多观察者 主题对象 狗对象 鸭子对象 老鼠对象 猫对象 老鼠对象要求从观察者中把自己除名。 老鼠已经观察此主题太久,厌倦了,所以决定不再当个观察者。 我要删除自己(取消订阅) 许多观察者
观察者模式的活动(3) 主题对象 老鼠离开了! 主题知道老鼠的请求之后,把它从观察者中除名。 老鼠对象 主题 一个新的整数 狗对象 猫地象 鸭子对象 主题对象 老鼠离开了! 主题知道老鼠的请求之后,把它从观察者中除名。 老鼠对象 许多观察者 主题 一个新的整数 除了老鼠之外,每个观察者都会收到通知,因为它已经被除名了。嘘!不要告诉别人,老鼠其实暗暗地怀念这些整数,或许哪天又会再次注册,回来继续当观察者呢! 主题对象 狗对象 猫地象 鸭子对象 14 14 14 14 老鼠对象 许多观察者
定义观察者模式 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态,它的所有依赖者都会收到通知并自动更新。 Subject Attach(Observer) Detach(Observer) Notify() Observer Update() for all 0 in observers 0→Update() ConcreteObserver Update() observerState observerState =subject →GetState() ConcreteSubject GetState() SetState() subjectState Return subjectState 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态,它的所有依赖者都会收到通知并自动更新。
实现气象站(1) public interface Subject { 这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。 public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyOberver(); } public interface Observer { public void update (float temp,float humidity,float pressure); public interface DisplayElement { public void display(); 当主题状态改变时,这个方法会被调用,以通知所有的观察者。 所有的观察者都必须实现update()方法,以实现观察者接口。在这里,我们按照Mary和Sue的想法把观测值传入观察者中。 DisplayElement接口只包含了一个方法,也是就是display(),当布告板需要显示时,调用此方法。
实现气象站(2) WeatherData现在实现了Subject接口。 public class WeatherData implements Subject { private ArrayList observer; private float temperature; private float humidity; private float pressure; public WeatherData() { observer =new ArrayList(); } public void registerObserver(Observer o) { observers.add(o); public void removeObserver(Observer o) { int i=observers.indexOf(o); if (i >=0) { observers.remove(i); public void notifyObservers() { for (int i=0;i<observers.size();i++) { Observer observer=(Observer)observer.get(i); observer.update(temperature,humidity,pressure); [ WeatherData现在实现了Subject接口。 我们加上一个ArrayList来纪录观察者,此ArrayList是在构造器中建立的。 当注册观察者时,我们只要把它加到ArrayList的后面即可。 同样地,在观察者想取消注册,我们把它从ArrayList中删除即可。 有趣的地方来了!在这里,我们把状态告诉每一个观察值得。因为观察都实现 update(),所以我们知道如何通知它们。
实现气象站(3) 当从气象站得到更新观测值时,我们通知观察者。 public void measurementsChanged() { notifyObservers(); } public void setMeasurements(float temperature,float humidity,float pressure) { this.temperature =temperature; this.humidity =humidity; this.pressure =pressure; measurementsChanged(); public class CurrentConditionsDisplay implements Observer,DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData=weatherData; weatherData.registerObserver (this); public void update(float temperature,float humidity, float pressure) { this.temperature=temperature; display(); public void display () { System.out.println(“Current conditions:” +temperature +”F degrees and” +humidity + “% humidity”); 此布告板实现了Observer接口,所以可以用WeatherData对象中获得改变。也实现了DisplayElement接口,因为我们的API规定所有的布告板都必须实现此接口。 构造器需要weatherData对象(也就是主题)作为注册之用。 当update()被调用时,我们把温度 湿度保存起来,然后调用display()。 display()方法就只是把最近的温度和湿度显示出来。
实现气象站(4) public class WeatherStation { public static void main(String[] args) { WeatherData weatherData =new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData); ForecastDisplay forecastDisplay=new ForecastDisplay(weatherData); weatherData.setMeasurements(80,65,30,4 f); weatherData.setMeasurements(82,70,29,2 f); weatherData.setMeasurements(78,90,29,2 f); } 首先建立一个WeatherData对象。 建立三个布告板,并把WeatherData对象传给它们。 模拟新的气象测量。