Q1:设计模式有哪些原则?
开闭原则:OOP 中最基础的原则,指一个软件实体(类、模块、方法等)应该对扩展开放,对修改关闭。尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化。强调用抽象构建框架,用实现扩展细节,提高代码的可复用性和可维护性。
单一职责原则:一个类、接口或方法只负责一个职责,降低代码复杂度以及变更引起的风险。
依赖倒置原则:即面相接口编程。程序应该依赖于抽象类或接口,而不是具体的实现类。
接口隔离原则:将不同功能定义在不同接口中实现接口隔离,避免了类依赖它不需要的接口,减少了接口之间依赖的冗余性和复杂性。
里氏替换原则:开闭原则的补充,规定了任何父类可以出现的地方子类都一定可以出现,可以约束继承泛滥,加强程序健壮性。子类可以扩展父类的功能,但不能改变父类原有的功能
迪米特原则:也叫最少知道原则,每个模块对其他模块都要尽可能少地了解和依赖,降低代码耦合度。
合成/聚合原则:尽量使用组合(has-a)/聚合(contains-a)而不是继承(is-a)达到软件复用的目的,避免滥用继承带来的方法污染和方法爆炸,方法污染指父类的行为通过继承传递给子类,但子类并不具备执行此行为的能力;方法爆炸指继承树不断扩大,底层类拥有的方法过于繁杂,导致很容易选择错误。
点击展开
开放封闭原则(Open Close Principle)
- 原则思想:软件实体(类、模块、方法等)应该对扩展开放,对修改关闭。尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
- 描述:一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。
- 优点:单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。
里氏代换原则(Liskov Substitution Principle)
- 原则思想:使用的基类可以在任何地方使用继承的子类,完美的替换基类。
- 大概意思是:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
- 优点:增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。
依赖倒转原则(Dependence Inversion Principle)
依赖倒置原则的核心思想是面向接口编程.
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,
这个是开放封闭原则的基础,具体内容是:对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则(Interface Segregation Principle)
- 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
- 例如:支付类的接口和订单类的接口,需要把这俩个类别的接口变成俩个隔离的接口
迪米特法则(最少知道原则)(Demeter Principle)
- 原则思想:一个对象应当对其他对象有尽可能少地了解,简称类间解耦
- 大概意思就是一个类尽量减少自己对其他对象的依赖,原则是低耦合,高内聚,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。
- 优点:低耦合,高内聚。
单一职责原则(Principle of single responsibility)
- 原则思想:一个方法只负责一件事情。
- 描述:单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。
- 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。
Q2:设计模式的分类,你知道哪些设计模式?
创建型: 在创建对象的同时隐藏创建逻辑,不使用 new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。包括工厂/抽象工厂/单例/建造者/原型模式。
结构型: 通过类和接口间的继承和引用实现创建复杂结构的对象。包括适配器/桥接模式/过滤器/组合/装饰器/外观/享元/代理模式。
行为型: 通过类之间不同通信方式实现不同行为。包括责任链/命名/解释器/迭代器/中介者/备忘录/观察者/状态/策略/模板/访问者模式。
Q3:说一说简单工厂模式
简单工厂模式指由一个工厂对象来创建实例,客户端不需要关注创建逻辑,只需提供传入工厂的参数。
适用于工厂类负责创建对象较少的情况,缺点是如果要增加新产品,就需要修改工厂类的判断逻辑,违背开闭原则,且产品多的话会使工厂类比较复杂。
Calendar 抽象类的 getInstance
方法,调用 createCalendar
方法根据不同的地区参数创建不同的日历对象。
Spring 中的 BeanFactory 使用简单工厂模式,根据传入一个唯一的标识来获得 Bean 对象。
示例
简单工厂模式通过一个工厂类来创建对象,而不是直接在代码中实例化对象。这样可以将对象的创建过程与使用过程分离,提高代码的可维护性和扩展性。
示例代码:
Java
// 产品接口
public interface Product {
void use();
}
// 具体产品类A
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用产品A");
}
}
// 具体产品类B
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用产品B");
}
}
// 工厂类
public class SimpleFactory {
public static Product createProduct(String type) {
if (type.equals("A")) {
return new ConcreteProductA();
} else if (type.equals("B")) {
return new ConcreteProductB();
}
return null;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Product productA = SimpleFactory.createProduct("A");
productA.use();
Product productB = SimpleFactory.createProduct("B");
productB.use();
}
}
Q4:说一说工厂方法模式
工厂方法模式指定义一个创建对象的接口,让接口的实现类决定创建哪种对象,让类的实例化推迟到子类中进行。
客户端只需关心对应工厂而无需关心创建细节,主要解决了产品扩展的问题,在简单工厂模式中如果产品种类变多,工厂的职责会越来越多,不便于维护。
Collection 接口这个抽象工厂中定义了一个抽象的 iterator
工厂方法,返回一个 Iterator 类的抽象产品。该方法通过 ArrayList 、HashMap 等具体工厂实现,返回 Itr、KeyIterator 等具体产品。
Spring 的 FactoryBean 接口的 getObject
方法也是工厂方法。
示例
// 产品接口
public interface Product {
void use();
}
// 具体产品类A
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用产品A");
}
}
// 具体产品类B
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用产品B");
}
}
// 工厂接口
public interface Factory {
Product createProduct();
}
// 具体工厂类A
public class ConcreteFactoryA implements Factory {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂类B
public class ConcreteFactoryB implements Factory {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Factory factoryA = new ConcreteFactoryA();
Product productA = factoryA.createProduct();
productA.use();
Factory factoryB = new ConcreteFactoryB();
Product productB = factoryB.createProduct();
productB.use();
}
}
Q5:抽象工厂模式了解吗?
抽象工厂模式指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们的具体类。 抽象工厂模式通过组合多个工厂方法来实现对象的创建。
客户端不依赖于产品类实例如何被创建和实现的细节,主要用于系统的产品有多于一个的产品族,而系统只消费其中某一个产品族产品的情况。抽象工厂模式的缺点是不方便扩展产品族,并且增加了系统的抽象性和理解难度。
java.sql.Connection 接口就是一个抽象工厂,其中包括很多抽象产品如 Statement、Blob、Savepoint 等。
示例
// 抽象产品接口A
public interface ProductA {
void use();
}
// 抽象产品接口B
public interface ProductB {
void use();
}
// 具体产品类A1
public class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("使用产品A1");
}
}
// 具体产品类A2
public class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("使用产品A2");
}
}
// 具体产品类B1
public class ConcreteProductB1 implements ProductB {
@Override
public void use() {
System.out.println("使用产品B1");
}
}
// 具体产品类B2
public class ConcreteProductB2 implements ProductB {
@Override
public void use() {
System.out.println("使用产品B2");
}
}
// 抽象工厂接口
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 具体工厂类1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
// 具体工厂类2
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.use();
productB1.use();
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
productA2.use();
productB2.use();
}
}
Q6:单例模式的特点是什么?
单例模式属于创建型模式,一个单例类在任何情况下都只存在一个实例,构造方法必须是私有的、由自己创建一个静态变量存储实例,对外提供一个静态公有方法获取实例。
优点是内存中只有一个实例,减少了开销,尤其是频繁创建和销毁实例的情况下并且可以避免对资源的多重占用。缺点是没有抽象层,难以扩展,与单一职责原则冲突。
Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象,还有 ServletContext、数据库连接池等也都是单例模式。
Q7:单例模式有哪些实现?
饿汉式:在类加载时就初始化创建单例对象,线程安全,但不管是否使用都创建对象可能会浪费内存。
public class HungrySingleton {
private HungrySingleton(){}
private static HungrySingleton instance = new HungrySingleton();
public static HungrySingleton getInstance() {
return instance;
}
}
懒汉式:在外部调用时才会加载,线程不安全,可以加锁保证线程安全但效率低。
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton instance;
public static LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重检查锁:使用 volatile 以及多重检查来减小锁范围,提升效率。
public class DoubleCheckSingleton {
private DoubleCheckSingleton(){}
private volatile static DoubleCheckSingleton instance;
public static DoubleCheckSingleton getInstance() {
if(instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
静态内部类:同时解决饿汉式的内存浪费问题和懒汉式的线程安全问题。
public class StaticSingleton {
private StaticSingleton(){}
public static StaticSingleton getInstance() {
return StaticClass.instance;
}
private static class StaticClass {
private static final StaticSingleton instance = new StaticSingleton();
}
}
枚举:《Effective Java》提倡的方式,不仅能避免线程安全问题,还能防止反序列化重新创建新的对象,绝对防止多次实例化,也能防止反射破解单例的问题。
特点:
- 线程安全:枚举类型的实例创建是线程安全的,JVM 保证了枚举实例的唯一性。
- 防止反序列化:枚举类型在反序列化时不会创建新的实例。
- 防止反射攻击:枚举类型不能通过反射创建实例。
public enum EnumSingleton {
INSTANCE;
// 可以添加其他方法和字段
public void doSomething() {
System.out.println("Doing something...");
}
}
public class Client {
public static void main(String[] args) {
EnumSingleton singleton = EnumSingleton.INSTANCE;
singleton.doSomething();
}
}
Q8:讲一讲代理模式
代理模式属于结构型模式,为其他对象提供一种代理以控制对这个对象的访问。优点是可以增强目标对象的功能,降低代码耦合度,扩展性好。缺点是在客户端和目标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度。
Spring 利用动态代理实现 AOP,如果 Bean 实现了接口就使用 JDK 代理,否则使用 CGLib 代理。
静态代理:代理对象持有被代理对象的引用,调用代理对象方法时也会调用被代理对象的方法,但是会在被代理对象方法的前后增加其他逻辑。需要手动完成,在程序运行前就已经存在代理类的字节码文件,代理类和被代理类的关系在运行前就已经确定了。 缺点是一个代理类只能为一个目标服务,如果要服务多种类型会增加工作量。
静态代理
// 接口
public interface UserService {
void addUser(String user);
}
// 目标对象
public class UserServiceImpl implements UserService {
@Override
public void addUser(String user) {
System.out.println("添加用户: " + user);
}
}
// 代理类
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String user) {
System.out.println("日志记录: 开始添加用户");
userService.addUser(user);
System.out.println("日志记录: 添加用户完成");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userService);
proxy.addUser("张三");
}
}
动态代理:动态代理在程序运行时通过反射创建具体的代理类,代理类和被代理类的关系在运行前是不确定的。动态代理的适用性更强,主要分为 JDK 动态代理和 CGLib 动态代理。
- JDK 动态代理:通过
Proxy
类的newInstance
方法获取一个动态代理对象,需要传入三个参数,被代理对象的类加载器、被代理对象实现的接口,以及一个InvocationHandler
调用处理器来指明具体的逻辑,相比静态代理的优势是接口中声明的所有方法都被转移到InvocationHandler
的invoke
方法集中处理。 - CGLib 动态代理:JDK 动态代理要求实现被代理对象的接口,而 CGLib 要求继承被代理对象,如果一个类是 final 类则不能使用 CGLib 代理。两种代理都在运行期生成字节码,JDK 动态代理直接写字节码,而 CGLib 动态代理使用 ASM 框架写字节码,ASM 的目的是生成、转换和分析以字节数组表示的已编译 Java 类。 JDK 动态代理调用代理方法通过反射机制实现,而 GCLib 动态代理通过 FastClass 机制直接调用方法,它为代理类和被代理类各生成一个类,该类为代理类和被代理类的方法分配一个 int 参数,调用方法时可以直接定位,因此调用效率更高。
点击展开
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
public interface UserService {
void addUser(String user);
}
// 目标对象
public class UserServiceImpl implements UserService {
@Override
public void addUser(String user) {
System.out.println("添加用户: " + user);
}
}
// 调用处理器
public class UserServiceInvocationHandler implements InvocationHandler {
private Object target;
public UserServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志记录: 开始添加用户");
Object result = method.invoke(target, args);
System.out.println("日志记录: 添加用户完成");
return result;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserServiceInvocationHandler handler = new UserServiceInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
handler);
proxy.addUser("张三");
}
}
Q9:讲一讲装饰器模式
装饰器模式属于结构型模式,在不改变原有对象的基础上将功能附加到对象,相比继承可以更加灵活地扩展原有对象的功能。
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许在不修改现有类的情况下动态地给对象添加新的功能。装饰器模式通过创建一个装饰器类来包装原始类,从而在保持类接口不变的情况下扩展对象的功能。
装饰器模式适合的场景:在不想增加很多子类的前提下扩展一个类的功能。
java.io 包中,InputStream 字节输入流通过装饰器 BufferedInputStream 增强为缓冲字节输入流。
点击展开
// 组件接口
public interface Component {
void operation();
}
// 具体组件类
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("基本操作");
}
}
// 装饰器类
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰器类A
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("装饰器A的额外行为");
}
}
// 具体装饰器类B
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("装饰器B的额外行为");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(component);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
}
}
Q11:讲一讲适配器模式
适配器模式属于结构型模式,它作为两个不兼容接口之间的桥梁,结合了两个独立接口的功能,将一个类的接口转换成另外一个接口使得原本由于接口不兼容而不能一起工作的类可以一起工作。
缺点是过多使用适配器会让系统非常混乱,不易整体把握。
java.io 包中,InputStream 字节输入流通过适配器 InputStreamReader 转换为 Reader 字符输入流。
Spring MVC 中的 HandlerAdapter,由于 handler 有很多种形式,包括 Controller、HttpRequestHandler、Servlet 等,但调用方式又是确定的,因此需要适配器来进行处理,根据适配规则调用 handle 方法。
Arrays.asList 方法,将数组转换为对应的集合(注意不能使用修改集合的方法,因为返回的 ArrayList 是 Arrays 的一个内部类)。
点击展开
// 旧接口
public interface OldInterface {
void oldRequest();
}
// 旧接口的实现类
public class OldClass implements OldInterface {
@Override
public void oldRequest() {
System.out.println("旧接口的请求");
}
}
// 新接口
public interface NewInterface {
void newRequest();
}
// 适配器类
public class Adapter implements NewInterface {
private OldInterface oldInterface;
public Adapter(OldInterface oldInterface) {
this.oldInterface = oldInterface;
}
@Override
public void newRequest() {
oldInterface.oldRequest();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
OldInterface oldObject = new OldClass();
NewInterface adapter = new Adapter(oldObject);
adapter.newRequest();
}
}
Q13:讲一讲策略模式
策略模式属于行为型模式,定义了一系列算法并封装起来,之间可以互相替换。策略模式主要解决在有多种算法相似的情况下,使用 if/else 所带来的难以维护。
优点是算法可以自由切换,可以避免使用多重条件判断并且扩展性良好,缺点是策略类会增多并且所有策略类都需要对外暴露。
在集合框架中,经常需要通过构造方法传入一个比较器 Comparator 进行比较排序。Comparator 就是一个抽象策略,一个类通过实现该接口并重写 compare 方法成为具体策略类。
创建线程池时,需要传入拒绝策略,当创建新线程使当前运行的线程数超过 maximumPoolSize 时会使用相应的拒绝策略处理。
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。策略模式使得算法可以独立于使用它的客户端而变化。
策略模式的主要特点:
- 策略接口:定义了一个算法族的公共接口。
- 具体策略类:实现了策略接口,包含具体的算法实现。
- 上下文类:持有一个策略接口的引用,并通过该引用调用具体策略类的方法。
策略模式的优点:
- 符合开闭原则:可以通过增加新的策略类来扩展新的算法,而不需要修改现有代码。
- 避免多重条件语句:通过使用策略模式,可以避免在客户端代码中使用多重条件语句来选择算法。
- 提高代码的灵活性和可维护性:将算法的实现与使用分离,使得代码更加灵活和可维护。
策略模式的缺点:
- 增加类的数量:每个具体策略类都需要实现策略接口,可能会导致类的数量增加。
- 客户端必须知道所有策略:客户端需要了解所有的策略类,并自行选择合适的策略。
点击展开
// 策略接口
public interface DiscountStrategy {
double calculateDiscount(double price);
}
// 具体策略类A:无折扣
public class NoDiscountStrategy implements DiscountStrategy {
@Override
public double calculateDiscount(double price) {
return price;
}
}
// 具体策略类B:百分比折扣
public class PercentageDiscountStrategy implements DiscountStrategy {
private double percentage;
public PercentageDiscountStrategy(double percentage) {
this.percentage = percentage;
}
@Override
public double calculateDiscount(double price) {
return price * (1 - percentage / 100);
}
}
// 具体策略类C:固定金额折扣
public class FixedAmountDiscountStrategy implements DiscountStrategy {
private double amount;
public FixedAmountDiscountStrategy(double amount) {
this.amount = amount;
}
@Override
public double calculateDiscount(double price) {
return price - amount;
}
}
// 上下文类
public class PriceCalculator {
private DiscountStrategy discountStrategy;
public PriceCalculator(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculatePrice(double price) {
return discountStrategy.calculateDiscount(price);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
double price = 100.0;
// 使用无折扣策略
PriceCalculator calculator = new PriceCalculator(new NoDiscountStrategy());
System.out.println("无折扣价格: " + calculator.calculatePrice(price));
// 使用百分比折扣策略
calculator = new PriceCalculator(new PercentageDiscountStrategy(10));
System.out.println("百分比折扣价格: " + calculator.calculatePrice(price));
// 使用固定金额折扣策略
calculator = new PriceCalculator(new FixedAmountDiscountStrategy(20));
System.out.println("固定金额折扣价格: " + calculator.calculatePrice(price));
}
}
Q15:讲一讲观察者模式
观察者模式属于行为型模式,也叫发布订阅模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。主要解决一个对象状态改变给其他对象通知的问题,缺点是如果被观察者对象有很多的直接和间接观察者的话通知很耗时, 如果存在循环依赖的话可能导致系统崩溃,另外观察者无法知道目标对象具体是怎么发生变化的。
ServletContextListener 能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用。当 Servlet 容器启动 Web 应用时调用 contextInitialized
方法,终止时调用 contextDestroyed
方法。
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,所有依赖于它的观察者对象都会得到通知并自动更新。
观察者模式的主要特点:
- 主题接口:定义了注册、移除和通知观察者的方法。
- 具体主题类:实现了主题接口,维护观察者列表,并在状态变化时通知所有观察者。
- 观察者接口:定义了更新方法,当主题状态变化时,观察者会被通知。
- 具体观察者类:实现了观察者接口,具体实现更新方法。
观察者模式的优点:
- 解耦:观察者模式将观察者与主题解耦,使得它们可以独立变化。
- 灵活性高:可以在运行时动态地添加或移除观察者。
- 符合开闭原则:可以通过增加新的观察者类来扩展新的功能,而不需要修改现有代码。
观察者模式的缺点:
- 通知开销:当观察者数量较多时,通知所有观察者可能会带来一定的开销。
- 依赖关系过多:观察者模式建立了一种紧密的依赖关系,可能会导致系统复杂性增加。
点击展开
// 主题接口
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 具体主题类
public class WeatherData implements Subject {
private List observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
// 观察者接口
public interface Observer {
void update(float temperature, float humidity, float pressure);
}
// 具体观察者类
public class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
@Override
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");
}
}
// 客户端代码
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
weatherData.registerObserver(currentDisplay);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
}
}
Q16:建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,它通过使用多个简单的对象一步一步构建一个复杂的对象。建造者模式将对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的主要特点:
- 建造者接口:定义了创建复杂对象各个部分的方法。
- 具体建造者类:实现了建造者接口,负责构建复杂对象的各个部分。
- 产品类:表示被构建的复杂对象。
- 指挥者类:负责管理建造过程,调用建造者的方法来构建复杂对象。
建造者模式的优点:
- 分步构建:可以一步一步地构建复杂对象,控制对象的创建过程。
- 符合单一职责原则:将对象的创建过程与表示分离,使得代码更加清晰和可维护。
- 灵活性高:可以通过不同的具体建造者类创建不同的表示。
建造者模式的缺点:
- 增加代码复杂度:需要定义多个建造者类和指挥者类,可能会增加代码的复杂度。
- 构建过程固定:建造者模式适用于构建过程固定的复杂对象,如果构建过程变化较大,可能不适用。
点击展开
// 产品类
public class Computer {
private String CPU;
private String RAM;
private String storage;
private String GPU;
public void setCPU(String CPU) {
this.CPU = CPU;
}
public void setRAM(String RAM) {
this.RAM = RAM;
}
public void setStorage(String storage) {
this.storage = storage;
}
public void setGPU(String GPU) {
this.GPU = GPU;
}
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", storage=" + storage + ", GPU=" + GPU + "]";
}
}
// 建造者接口
public interface ComputerBuilder {
void buildCPU();
void buildRAM();
void buildStorage();
void buildGPU();
Computer getComputer();
}
// 具体建造者类
public class GamingComputerBuilder implements ComputerBuilder {
private Computer computer;
public GamingComputerBuilder() {
this.computer = new Computer();
}
@Override
public void buildCPU() {
computer.setCPU("High-end CPU");
}
@Override
public void buildRAM() {
computer.setRAM("16GB RAM");
}
@Override
public void buildStorage() {
computer.setStorage("1TB SSD");
}
@Override
public void buildGPU() {
computer.setGPU("High-end GPU");
}
@Override
public Computer getComputer() {
return computer;
}
}
// 指挥者类
public class Director {
private ComputerBuilder builder;
public Director(ComputerBuilder builder) {
this.builder = builder;
}
public void construct() {
builder.buildCPU();
builder.buildRAM();
builder.buildStorage();
builder.buildGPU();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ComputerBuilder builder = new GamingComputerBuilder();
Director director = new Director(builder);
director.construct();
Computer computer = builder.getComputer();
System.out.println(computer);
}
}
Q17:外观模式
外观模式:也叫门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。 它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性。 使用外观模式,他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现
Q18:原型模式
原型设计模式简单来说就是克隆,原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有对象来创建新的对象,而不是通过实例化类来创建对象。原型模式适用于创建对象成本较高或复杂的场景,通过复制原型对象来提高性能和简化对象创建过程。
原型模式的主要特点:
- 原型接口:定义了一个用于克隆自身的接口。
- 具体原型类:实现了原型接口,包含克隆自身的方法。
- 客户端:通过调用原型对象的克隆方法来创建新的对象。
原型模式的优点:
- 提高性能:通过复制现有对象来创建新对象,避免了重复的初始化操作,提高了性能。
- 简化对象创建:通过克隆原型对象来创建新对象,简化了对象的创建过程。
- 动态扩展:可以在运行时动态地创建和修改对象,而不需要修改类的定义。
原型模式的缺点:
- 深拷贝和浅拷贝:实现深拷贝可能比较复杂,需要处理对象中的引用类型。
- 对象复制的复杂性:某些对象的复制过程可能比较复杂,需要额外的处理。
点击展开
// 原型接口
public interface Shape extends Cloneable {
Shape clone();
void draw();
}
// 具体原型类:圆形
public class Circle implements Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public Circle clone() {
try {
return (Circle) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public void draw() {
System.out.println("绘制圆形,半径:" + radius);
}
}
// 具体原型类:矩形
public class Rectangle implements Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public Rectangle clone() {
try {
return (Rectangle) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public void draw() {
System.out.println("绘制矩形,宽度:" + width + ",高度:" + height);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Circle circle = new Circle(5);
Circle clonedCircle = circle.clone();
clonedCircle.draw();
Rectangle rectangle = new Rectangle(10, 20);
Rectangle clonedRectangle = rectangle.clone();
clonedRectangle.draw();
}
}