zoukankan      html  css  js  c++  java
  • Head First Design Patterns

    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();
    		}
    	}
    }
    
  • 相关阅读:
    笔记:一篇关于容器和虚拟机的对比
    语义化版本说明脑图
    KiCad EDA 5.1.4 发布了
    KiCad 5.1.4 无法覆铜?
    mac 常用的终端命令
    PC 商城扫描二维码登录
    Git的撤销与回滚
    springboot 集成elasticsearch5.4.3
    redis 缓存类型为map
    基于Elasticsearch 5.4.3的商品搜索系统
  • 原文地址:https://www.cnblogs.com/kid551/p/4389314.html
Copyright © 2011-2022 走看看