From Head First Design Patterns.

Design Principle:

Idnetify the aspects of your application that vary and separate them from what stays the same.

Here's another way to think about it:

Take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don't.


SimUDuck App

We need to separate behaviors from Duck class. So, let's define two behavior interfaces: QuackBehavior and FlyBehavior with additonal instantial classes.

Design Principle

Program to an interface, not an implementation.

QuackBehavior interface and instantial classes:

public interface QuackBehavior {
public void quack();
} public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
} public class MuteQuack implements QuackBehavior {
public void quack(){
System.out.println("<< Silence >>");
}
} public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}

FlyBehavior interface and instantial classes:

public interface FlyBehavior {
public void fly();
} public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
} public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}

And then, we can define the abstract class Duck, which is the base class of our Duck Project:

public abstract class Duck {
QuackBehavior quackBehavior;
FlyBehavior flyBehavior; public Duck() { } public abstract void display(); public void performQuack() {
quackBehavior.quack();
} public void performFly() {
flyBehavior.fly();
} public void swim() {
System.out.println("All ducks float, even decoys!");
}
}

Now, we can make a concrete duck, Mallard!

public class MallardDuck extends Duck {

	public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
} public void display() {
System.out.println("I'm a real Mallard duck");
}
}

Finally,let's write a  simulator to test our codes:

public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
mallard.display();
}
} /* Output:
Quack
I'm flying!!
I'm a real Mallard duck
*/

Pay attention, the duck mallard here only offer two APIs: performQuack(), performFly(). We can't call quack() or fly(), which also make the process of calling behaviors simple.

It's a shame to have all dynamic talent built into our ducks and not be using it!

If you want to set duck's behavior now, you should make it through instantiating it in the duck's constructor rather than adding a setter method on the duck subclass.

Add two new methods to the Duck class:

public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
} public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}

Let's make a new Duck type:

public class ModelDuck extends Duck {
public ModelDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyNoWay();
} public void display() {
System.out.println("I'm a model duck");
}
}

adding new FlyBehavior type:

public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I'm flying with a rocket!");
}
}

Now, we can add some codes in simulator to test our set methods:

Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();

Here, the model duck dynamically changed its flying behavior!

You can't do THAT if the implementation lives inside the duck class!

Design Principle

Favor composition over inheritance.

In this project, we use the Stragegy pattern, the formal definition is:

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Libraries and frameworks can't help us structure our own applications in ways that are easier to understand, more maintainable and flexible.

Design patterns are higher level than libraries. Design patterns tell us how to structure classes and objects to solve certain problems and it is our job to adapt those designs to fit our particular application. And it can help you more quickly understand APIs that are structured around design patterns.

Misunderstandings of object-oriented development:

by know the OO basics we are automatically going to be good at building flexible, reusable, and maintainable systems.

A design guru always thinks about how to create flexible designs that are maintaqinable and that can cope with change.


Weather Monitoring application

Publishers + Subscribers = Observer Pattern

You know how newspaper or magazine subscriptions work:

  1. A newspaper publisher goes into business and begins publishing newpapers.
  2. You subscribe to a particular publisher, and every time there's a new edition it gets delivered to you. As long as you remain a subscriber, you get new newspaper.
  3. You unsubscribe when you don't want papers anymoe, and they stop delivered.
  4. While the publisher remains in business, people, hotels, airlines and other businesses constantly subscribe and unsubscribe to the newspaper.

In the Observer Pattern, we call the publisher the SUBJECT, and the subscribers the OBSERVERS.

Formal definition:

The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

Loosely coupled designs allow us to build flexible OO systems that can handle change because they minimize the interdependency between objects.

Design Principle

Strive for loosely coupled designs between objects that interact.

The classes design:

  • Our WeatherData class is the "one".
  • Various display elements are the "many".
  • State is: temperature, humidity and harometric pressure.

If we make the WeatherData object the subject, and the display elements the observers, then the displays will register themselves with the WeatherData object in order to get information they want.

As every element can be different, all the components should implement the same interface so that the WeatherData object will know how to send them the measurements. So every display will have an updata() method that WeatherData will call.

The design diagram should be:

Implement the Weather Station

While in some cases you can make use of Java's built-in support, in a lot of cases it's more flexible to build your own (and it's not all that hard). So, let's get started with the interfaces:

public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
} public interface Observer {
public void update(float temp, float humidity, float pressure);
} public interface DisplayElement {
public void display();
}

Here, passing the measurements directly to the observers was the most straightforward method of updating state.

  • Is this an area of the application that might change in the future?
  • If it did change, would the change be well encapsulated?
  • Or would it require changes in many parts of the code?

Implement the Subject interface in WeatherData:

public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure; public WeatherData() {
observers = 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)observers.get(i);
observer.update(temperature, humidity, pressure);
}
} public void measurementsChanged() {
notifyObservers();
} public void setMeasuremets(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
} // other WeatherData methods here. }

Now that we've got our WeatherData class straightened out, it's time to build the Display Elements:

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;
this.humidity = humidity;
display();
} public void display() {
System.out.println("Current conditions: " + temperature +"F degrees and " + humidity + "% humidity");
} }

Q: Is update() the best place to call display() ?

A: There're much better ways to desgin the way the data gets displayed. We're going to se this when we get to the model-view-controller pattern.

Q: Why did you store a reference to the Subject? It doesn't look like you use it again after the constructor?

A: True, but in the future we may want to un-register ourselves as an observer and it would be handy to already have a reference to the subject.

The Weather Station is ready to go, all we need is some coe to glue everything together:

public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); weatherData.setMeasuremets(80, 65, 30.4f);
weatherData.setMeasuremets(82, 70, 29.2f);
weatherData.setMeasuremets(78, 90, 29.2f);
}
} /* Output:
Current conditions: 80.0F degrees and 65.0% humidity
Current conditions: 82.0F degrees and 70.0% humidity
Current conditions: 78.0F degrees and 90.0% humidity
*/

Using Java's built-in Observer Pattern

The most general is the Observer interface and the Observable class in the java.util package.

With Java's built-in support, all you have to do is extend Observable and tell t when to notify the Observers. The API does the rest for you.

To get a high level feel for java.util.Observer and java.util.Observable, check out this reworked OO desgin for the WeatherStation:

The Observable class keeps track of all your observers and notifies them for you.

Observable is a CLASS not an interface, so WeatherData extends Observable.

For the observable to send notifications:

First, you should be Observable by extending the java.util.Observable superclass.

  1. You first must call the setChanged() method to signify that the state has changed in your object.
  2. Then, call one of two notifyObservers() methods:
    • notifyObservers()
    • notifyObservers(Object arg)  (arg is an arbitrary data object)

For an observer to receive notifications:

It implements the update method, as before, but the signature of the method is a bit different:

update(Observable o, Object arg);
  • If you want to "push" data to the observers, you can pass the data as a data object to the notifyObservers(arg) method.
  • If not, then the Observer has to "pull" the data it wants from the Observable object object passed to it.

The setChanged() method is used to signify that the state has changed and that notifyObservers(), when it is called, should update its observers.

Pseudo code for the Observable class:

setChanged() {
changed = true
} notifyObservers(Object arg) {
if(changed) {
for every observer on the list {
call update(this, arg)
} changed = false
}
} notifyObservers() {
notifyObservers(null)
}

Why is this necessary? The setChanged() method is meant to give you more flexibility in how you update observers by allowing you to optimize the notifications.

For example, if our measurements were so sensitive that the temperature readings were constantly fluctuating by a few tenths of a degree. That might cause the WeatherData object to send out notifications constantly. Instead, we might want to send our notifications only if the temperature changes more than half a degree and we could call setChanged() only after that happend.

Reworking the Weather Station with the built-in support:

import java.util.Observable;
import java.util.Observer; public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure; public WeatherData() {} public void measurementsChanged() {
setChanged();
notifyObservers();
} public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
} public float getTemperature() {
return temperature;
} public float getHumdity() {
return Humidity;
} public float getPressure() {
return pressure;
}
}

Our constructor no longer needs to create a data structure to hold Observers.

We aren't sending a data object with the notifyObservers() call. That means we're using the PULL model.

Now, let's rework the CurrentConditionsDisplay:

import java.util.Observable;
import java.util.Observer; public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity; public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
} public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
} public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
} }

Reworking the ForecastDisplay class:

import java.util.Observable;
import java.util.Observer; public class ForecastDisplay implements Observer, DisplayElement {
private Observable observable;
private float currentPressure = 29.92f;
private float lastPressure; public ForecastDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
} public void update(Observable obs, Object arg) {
if(observable instanceof WeatherData){
WeatherData weatherData = (WeatherData)obs;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
} public void display() {
// display code here.
} }

The dark side of java.util.Observable

  • Because Observable is a class, you have to subclass it. That means you can't add on the Observable behavior to an existing class that already extends another superclass.
  • If you look at the Observer API, the setChanged() method is protected. This means you can't call setChanged() unless you've subclassed Observable. This means you can't even create an instance of the Observable class and compose it with your own objects, you have to subclass.

In either case, you know the Observer Pattern well and you're in a good position to work with any API that makes use of the pattern.

The java.util implementation of Observer/Observable is not the only place you'll find the Observer Pattern in the JDK. Both JavaBeans and Swing also provide their own implementations of the pattern. Let's look at a simple part of the Swing API, the JButton.

If you look under the hood at JButton's superclass, AbstractButton, you'll see that it has a lot of add/remove listener methods. Theses methods allow you to add and remove observers, or as they are called in Swing, listeners, to listen for various type of events that occure on the Swing component.

For instance, an ActionListener lets you "listen in" on any types of actions that might occur on a button, like a button press.

import javax.swing.*;

public class SwingObserverExample {
JFrame frame; public static void main(String[] args) {
SwingObserverExample example = new SwingObserverExample();
example.go();
} public void go() {
frame = new JFrame();
JButton button = new JButton("Should I do it?");
button.addActionListener(new AngelListener());
button.addActionListener(new DevilListener());
frame.getContentPane().add(BorderLayout.CENTER, button);
// Set frame properties here
} class AngelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println("Don't do it, you might regret it!");
}
} class DevilListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println("Come on, do it!");
}
} }

Starbuzz Coffee

We'll re-examine the typical overuse of inheritance and you'll learn how to decorate your classes at runtime using a form of object composition. Once you know the techniques of decorating, you'll be able to give your (or someone else's) objects new responsibilities without making any code changes to the underlying classes.

Design Principle

Classes should be open for extension, but closed for modification.

When we compose a decorator with a component, we are adding new behavior. We are acquiring new behavior not by inheriting it from a superclass, but by composing objects together.

Decroating our Beverages:

public abstract class Beverage {
String description = "Unknown Beverage"; public String getDescription() {
return description;
} public abstract double cost();
}

Beverage is simple enough. Let's implement the abstract class for the Condiments(Decorator) as well:

public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}

First, we need to be interchangeable with a Beverage, so we extend the Beverage class.

Then, we're going to require  that the condiment decorators all reimplement the getDescription() method.

Now we'll implement some beverage. We'll start with Epresso.

public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
} public double cost() {
return 1.99;
}
}

HouseBlend:

public class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
} public double cost() {
return .89;
}
}

Implement concrete decorators:

public class Mocha extends CondimentDecorator {
Beverage beverage; public Mocha(Beverage beverage) {
this.beverage = beverage;
} public String getDescription() {
return beverage.getDescription() + ", Mocha";
} public double cost() {
return .20 + beverage.cost();
} }

Finally

public class StarbuzzCoffe {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost() ); // a dark roast with double Mocha and whip.
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost() ); // a house blend with Soy Mocha and whip.
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription() + " $" + beverage3.cost() );
}
} /* Output:
Espresso $1.99
DarkRoast, Mocha, Mocha, Whip $1.49
HouseBlend, Soy, Mocha, Whip $1.34
*/

Write your own Java I/O Decorator:

import java.io.*;

public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
} public int read() throws IOException {
int c = super.read();
return (-1 == c ? 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;
} }

Test I/O decorator:

import java.io.*;

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(IOException e) {
e.printStackTrace();
}
}
} /* Output:
i know the decorator pattern thereform i rule!
*/

Factory Patterns

Let's say you have a pizza shop, and as a cutting-edge pizza store owner in Objectiville you might end up writing some code like this:

Pizza orderPizza() {
Pizza pizza = new Pizza(); pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box(); return pizza;
}

So then you'd add some code that determines the appropriate type of pizza and then goes about making the pizza:

Pizza orderPizza(String type) {
Pizza pizza; if(type.equals("cheese")) {
pizza = new CheesePizza();
} else if(type.equals("greek")) {
pizza = new GreekPizza();
} else if(type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box(); return pizza;
}

This code is NOT closed for modification. If the Pizza Shop changes its pizza offerings, we have to get into this code and modify it.

Clearly, dealing with which concrete class is instantiated is really messing up our orderPizza() method and preventing it from being closed for modification.

So now we know we'd better off moving the object creation out of the orderPizza() method.

We place that code in an object that is only going to worry about how to create pizzas. If any object needs a pizza created, this is the object to come to.

Build a simple pizza factory to define a class that encapsulates the object creation for all pizzas:

public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null; 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;
}
}

Now, it's time to fix client code. What we want to do is rely on the factory to create the pizzas for us:

public class PizzaStore {
SimplePizzaFactory factory; public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
} public Pizza orderPizza(String type) {
Pizza pizza; pizza = factory.createPizza(type); pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box(); return pizza;
} // other method here...
}

A framework for the pizza store

There's a way to localize all the pizza making activities to the PizzaStore class, and yet give the franchises freedom to have their own regional style.

What we're going to do is put the createPizza() method back into PizzaStore, but as an abstract method and then create a PizzaStore suclass for each regional style.

public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza; pizza = createPizza(type); pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box(); return pizza;
} abstract Pizza createPizza(String type);
}

Now we've got a store waiting for subclasses; we're going to have a subclass for each regional type and each subclass is going to make the decision about what makes up a pizza.

public class NYPizzaStore extends PizzaStore {
public Pizza createPizza(type) {
if(type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
} else if(type.equals("pepperoni")) {
pizza = new NYStylePepperoniPizza();
} else if(type.equals("clam")) {
pizza = new NYStyleClamPizza();
} else if(type.equals("veggie")) {
pizza = new NYStyleVeggiePizza();
}
}
} public class ChicagoPizzaStore extends PizzaStore {
public Pizza createPizza(type) {
if(type.equals("cheese")) {
pizza = new ChicagoStyleCheesePizza();
} else if(type.equals("pepperoni")) {
pizza = new ChicagoStylePepperoniPizza();
} else if(type.equals("clam")) {
pizza = new ChicagoStyleClamPizza();
} else if(type.equals("veggie")) {
pizza = new ChicagoStyleVeggiePizza();
}
}
} public class CaliforniaPizzaStore extends PizzaStore {
public Pizza createPizza(String type) {
if(type.equals("cheese")) {
pizza = new CaliforniaStyleCheesePizza();
} else if(type.equals("pepperoni")) {
pizza = new CaliforniaStylePepperoniPizza();
} else if(type.equals("clam")) {
pizza = new CaliforniaStyleClamPizza();
} else if(type.equals("veggie")) {
pizza = new CaliforniaStyleVeggiePizza();
}
}
}

Head First Design Patterns的更多相关文章

  1. Design Patterns Simplified - Part 3 (Simple Factory)【设计模式简述--第三部分(简单工厂)】

    原文链接:http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part3-factory/ Design ...

  2. Design Patterns Simplified - Part 2 (Singleton)【设计模式简述--第二部分(单例模式)】

    原文链接: http://www.c-sharpcorner.com/UploadFile/19b1bd/design-patterns-simplified-part-2-singleton/ De ...

  3. Apex Design Patterns

    Apex allows you to build just about any custom solution on the Force.com platform. But what are the ...

  4. [Design Patterns] 4. Creation Pattern

    设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结,使用设计模式的目的是提高代码的可重用性,让代码更容易被他人理解,并保证代码可靠性.它是代码编制真正实现工程化. 四个关键元素 ...

  5. [Design Patterns] 3. Software Pattern Overview

    When you're on the way which is unknown and dangerous, just follow your mind and steer the boat. 软件模 ...

  6. [Design Patterns] 1. Primary concept & term - UML

    It's time to review design patterns, especially when I reach the turning-point of my career. That's ...

  7. 设计模式(Design Patterns)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  8. Learning JavaScript Design Patterns The Observer Pattern

    The Observer Pattern The Observer is a design pattern where an object (known as a subject) maintains ...

  9. Learning JavaScript Design Patterns The Module Pattern

    The Module Pattern Modules Modules are an integral piece of any robust application's architecture an ...

随机推荐

  1. Search and Replace

    function myReplace(str, before, after) { //return str; if(before[0] === before[0].toUpperCase()){ af ...

  2. Android Studio 引入 Fresco

    首选在build.gradle文件中配置 查看NDK路径 然后在gradle.properties文件中配置 ndk.path=C\:\\Users\\lixishuang\\AppData\\Loc ...

  3. Cobar-Client 实现策略总结

    1. 数据源 DataSource CobarClient 的 DataSource 分为三层 ICobarDataSourceService: 封装了多个 DataSourceDescriptor, ...

  4. mybatis 关联查询 association

    <resultMap id="DutyPersonAndFileAndScoreMap" type="com.cares.asis.duty.entity.Duty ...

  5. c语言中动态数组的建立

    一维动态数组的创建,这个比较简单,直接上代码 #define _CRT_SECURE_NO_DEPRECATE #include<stdio.h> #include<stdlib.h ...

  6. ASP连接数据库登录按钮

    (1)在配置文件中加上以下代码: <connectionStrings> <add name="accessconn" connectionString=&quo ...

  7. Jquery DOM元素的方法

    jQuery DOM 元素方法 函数 描述 .get() 获得由选择器指定的 DOM 元素. .index() 返回指定元素相对于其他指定元素的 index 位置. .size() 返回被 jQuer ...

  8. 什么是领域驱动设计(Domain Driven Design)?

    本文是从 What is Domain Driven Design? 这篇文章翻译而来. ”…在很多领域,专家的作用体现在他们的专业知识上而不是智力上.“ -- Don Reinertsen 领域驱动 ...

  9. (并查集)~APTX4869(fzu 2233)

    http://acm.fzu.edu.cn/problem.php?pid=2233 Problem Description 为了帮助柯南回到一米七四,阿笠博士夜以继日地研究APTX4869的解药.他 ...

  10. Poj-2250-Compromise

    题意是找两篇文章中的最长子单词序列 能得出个数,但不知如何输出,找不到路径 看了别人的dfs,有所领悟: 若输入s1:ab,bd,fk,ce,ak,bt,cv s2: ab,fk,ce,tt,ak,b ...