1. 设计模式是什么
设计模式是一种在软件开发中常用的最佳实践,它是一系列解决常见问题并提供可复用解决方案的经验总结。设计模式并不是具体的代码实现,而是一个描述性的框架,用于结构化复杂的设计决策。它们通常分为三类:
- 创建型模式:如工厂模式、单例模式,关注如何创建对象,提高灵活性。
- 结构型模式:如适配器模式、装饰者模式,涉及类或对象组合,简化系统组件之间的交互。
- 行为型模式:如观察者模式、策略模式,专注于对象之间的通信和责任分配。
2. 代理
代理是一种设计模式,它允许一个对象在不影响其他对象的情况下改变其行为或外观。在软件系统中,代理通常用于远程访问、权限控制、性能优化或者负载均衡等场景。
-
远程访问:当需要通过网络访问远程对象时,可以使用代理作为桥梁,将客户端请求转发给实际服务提供者,从而隐藏网络延迟或通信复杂性。
-
权限控制:在安全系统中,代理可以作为访问控制的中间层,只有经过授权的代理才能代表用户操作受限资源。
-
性能优化:例如,对于频繁访问的数据,可以创建缓存代理,先从本地缓存查找,减少对数据库或其他慢速资源的依赖。
-
负载均衡:在网络服务器集群中,可以使用代理均衡器分配请求到不同的服务器,提高系统的可用性和响应速度。
3. 设计模式的原则
设计模式是一种在软件设计中反复出现的最佳实践,它提供了一种结构化的解决方案来解决常见问题。设计模式通常包含三个核心原则:
-
目的导向(Intent-driven):每个设计模式都有明确的目的,即解决特定的设计问题。例如,工厂模式用于创建对象而无需暴露其内部细节。
-
开闭原则(Open-Closed Principle,OCP):设计应对扩展开放,对修改封闭。意味着你可以增加新的功能,但不需要修改现有的代码结构。
-
里氏替换原则(Liskov Substitution Principle,LSP):子类可以替换掉它们的基类而不影响程序的正确运行。这保证了基类和子类之间的兼容性和灵活性。
-
单一职责原则(Single Responsibility Principle,SRP):一个类应该只负责一个责任或一个业务领域,这样使得代码更易于理解和维护。
-
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这有助于减少耦合度,提高系统稳定性。
-
SOLID原则:这是五个经典原则的集合,包括单一职责、开放封闭、依赖倒置、接口隔离和有限知识(Law of Demeter),共同构成了高质量设计的标准。
-
迪米特原则: 迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。英文简写为: LOD。
通过遵循这些原则,设计模式不仅能够提升代码的可复用性和可维护性,还能促进团队间的协作和知识传递。
4. 单例模式
创建对象,无论创建多少次,返回的使用一个对象
日志文件: 一个系统里面只能有一份日志文件
4.1 饿汉模式实现
首先,定义了一个私有的静态变量ehan1,它的类型是Ehan1,并且被初始化为一个新的Ehan1对象。
然后,定义了一个私有的构造方法,防止外部通过new关键字创建Ehan1对象。
最后,定义了一个公共的静态方法getInstance(),它返回ehan1对象,实现了单例模式的全局唯一性。
这样,外部只能通过调用getInstance()方法来获取Ehan1对象,而不能直接创建新的对象。这种方式保证了整个程序中只有一个Ehan1对象,可以用于需要全局唯一性的场景。
创建Test测试:
@Test
public void testEhan(){
// 获取Ehan1类的单例实例
Ehan1 ehan1 = Ehan1.getInstance();
// 输出实例以供检查
System.out.println(ehan1);
// 再次获取Ehan1类的单例实例
Ehan1 ehan2 = Ehan1.getInstance();
// 输出实例以供检查
System.out.println(ehan2);
// 检查两次获取的实例是否为同一个对象
System.out.println(ehan1 == ehan2);
// 以下注释掉的代码如果被取消注释,将创建一个新的Ehan1实例,用于对比单例模式的效果
// Ehan1 ehan2 = new Ehan1();
// System.out.println(ehan2 == ehan1);
}
运行结果为: true
4.2 枚举
要使用枚举,首先需要定义一个枚举类型,并在其中列出枚举常量。然后可以通过引用枚举类型和使用枚举常量来使用枚举。
定义一个枚举类型DanliMeiju,并且在这个枚举类型中只有一个实例INSTANCE。
public enum DanliMeiju {
INSTANCE
}
测试这个枚举类的单例模式是否生效:
public void testMeiju(){
// 枚举类在使用里面的而属性的时候返回的是对象本身
// 只加载一次
//
DanliMeiju instance = DanliMeiju.INSTANCE;
DanliMeiju instance2 = DanliMeiju.INSTANCE;
System.out.println(instance2==instance);
}
运行结果为: true
4.3 懒汉模式实现
懒汉模式是指在调用时才创建对象
public class Danli4 {
private Danli4(){}
// 对象 存放到静态内部类
// 内部类 还是一个类 class static
private static class Danli4Holder{
private static final Danli4 INSTANCE = new Danli4();
}
public static Danli4 getInstance(){
return Danli4Holder.INSTANCE;
}
}
创建对象
public class MyTest1 implements Runnable{
public void run() {
Danli4 instance = Danli4.getInstance();
System.out.println(instance);
}
}
这段代码定义了一个名为MyTest1的类,该类实现了Runnable接口。在run方法中,通过调用Danli4类的getInstance方法获取一个Danli4实例,并将其打印输出。
Test测试
public class MyTest2 {
@Test
public void test3() {
MyTest1 myTest1 = new MyTest1();
for (int i = 0; i < 10; i++) {
Thread t1 = new Thread(myTest1);
t1.start();
}
}
}
这段代码定义了一个名为MyTest2的类,其中包含一个名为test3的测试方法。该方法创建了一个MyTest1对象,并使用for循环启动了10个线程,每个线程都使用MyTest1对象作为目标来执行。
4.4 两种单例模式的优缺点
懒汉模式: 节省内存,在多线程情况下需要考虑安全性
饿汉模式: 占用内存,不需要考虑安全性,内存大使用饿汉模式
5. 工厂模式
工厂模式是一种创建型设计模式,它主要目的是封装对象的创建过程,工厂模式可以分为三类:
-
简单工厂模式:通过一个具体的工厂类来负责创建所有对象,客户端通过工厂类来获取所需的对象实例。这种模式的缺点是当系统需要引入新的产品类时,需要修改工厂类,违反了开闭原则。
-
工厂方法模式:定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口。这种模式可以在不修改具体工厂角色的情况下引进新的产品。
-
抽象工厂模式:当有多个抽象角色时使用的
public interface Car { void run(); void move(); }
一种工厂模式,用于产品族的构建。抽象工厂模式向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。这种模式适用于当客户端需要创建一些产品结构,而这些产品结构又分别属于不同的产品类别的情况。
5.1 简单工厂模式
简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离
设计一个汽车类(Car),并定义其两个子类(德系汽车和国产汽车);再设计一个工厂类,工厂可以生产汽车;最后设计一个4s店,4s店可以让工厂生产汽车。
首先编写接口:
public interface Car {
void run();
void move();
}
实现类:
public class DexiCar implements Car{
public void run() {
System.out.println("DexiCar run");
}
public void move() {
System.out.println("DexiCar move");
}
}
public class GuoChanCar implements Car{
public void run() {
System.out.println("GuoChanCar run");
}
public void move() {
System.out.println("GuoChanCar move");
}
}
这段代码定义了一个名为DexiCar的类和GuoChanCar类,两个类实现了Car接口。类中定义了两个方法:run和move。
工厂类:
负责创建对象
public class CarFactory {
/*
生产东西
* */
public Car createCar(String type) {
if(type.equals("gc")){
return new GuoChanCar();
} else if (type.equals("dx")) {
return new DexiCar();
} else if (type.equals("fx")) {
return new FaxiCar();
}
return null;
}
}
该函数根据传入的type参数创建不同类型的汽车对象并返回。如果type为"gc",则返回一个GuoChanCar对象,如果type为"dx",则返回一个DexiCar对象,如果type为"fx",则返回一个FaxiCar对象,如果type不匹配任何类型,则返回null。
实现类:
public class Test {
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
Car gc = carFactory.createCar("dx1");
gc.run();
gc.move();
}
}
这段代码定义了一个名为Test的Java类,其中包含一个main方法作为程序的入口点。在main方法中,首先创建了一个CarFactory对象,然后使用该对象的createCar方法创建了一个Car对象gc,并传入参数"dx1"。接下来,调用gc对象的run方法和move方法。
如果增加车的类型 工厂里面需要代码 违背开闭原则
5.2 工厂方法模式
工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。
首先编写接口:
public interface Car {
void fadongji();
void lunzi();
}
这段代码定义了一个名为Car的接口,其中包含两个抽象方法:fadongji()和lunzi()。该接口用于规范汽车类的行为,实现该接口的类必须实现这两个方法。
public interface ICarFactory {
Car createCar();
}
该函数定义了一个接口ICarFactory,其中包含一个方法createCar(),用于创建汽车对象。该接口可以被实现,实现类需要提供createCar()方法的具体实现,用于生成特定类型的汽车对象。
实现类:
public class ChineseCar implements Car{
public ChineseCar() {
fadongji();
lunzi();
}
public void fadongji() {
System.out.println("中国的发动机");
}
public void lunzi() {
System.out.println("中国的车轮");
}
}
public class FaXiCar implements Car{
public FaXiCar() {
fadongji();
lunzi();
}
public void fadongji() {
System.out.println("FaXiCar fadongji");
}
public void lunzi() {
System.out.println("FaXiCar lunzi");
}
}
这两段代码分别定义了一个类,实现了Car接口。类在构造函数中调用了fadongji()和lunzi()方法。
工厂类:
public class ChineseFactory implements ICarFactory{
public Car createCar() {
return new ChineseCar();
}
}
public class FaXiFactorry implements ICarFactory{
public Car createCar() {
return new FaXiCar();
}
}
两段代码定义了两个类,实现了ICarFactory接口。该类中有一个名为createCar的方法,用于创建一辆车。
测试类:
public class Test {
public static void main(String[] args) {
//
ICarFactory carFactory = new FaXiFactorry();
carFactory.createCar();
}
}
定义一个名为Test的类,其中包含一个main方法作为程序的入口点。在main方法中,通过实例化一个FaXiFactory对象并将其赋值给ICarFactory类型的变量carFactory,然后调用carFactory的createCar方法来创建汽车。
缺点:每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
5.3 抽象工厂
产品等级: 相同的东西 不同的品牌
抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
接口:
public interface BingXiang {
void baoxian();
}
public interface XiYiJi {
void xiyifu();
}
这两段代码定义了两个接口,其中包含一个抽象方法:baoxian()和xiyiji()。实现该接口的类必须实现这两个方法。
public interface IFactory {
BingXiang createBingXiang();
XiYiJi createXiYiJi();
}
该函数是一个接口IFactory,其中定义了两个方法createBingXiang和createXiYiJi。该接口的作用是提供一个工厂,用于创建BingXiang和XiYiJi对象。
实现类:
public class HaiErBingXiang implements BingXiang{
public void baoxian() {
System.out.println("海尔冰箱,进行保鲜");
}
}
public class MeiDiXiang implements BingXiang{
public void baoxian() {
System.out.println("美的冰箱,进行保鲜");
}
}
这两段代码定义了两个类,该类实现了BingXiang接口中的baoxian方法。baoxian方法的功能是在控制台输出内容。
public class HaiErXiyiji implements XiYiJi{
public void xiyifu() {
System.out.println("海尔洗衣机洗衣服");
}
}
public class MeiDiXiyiji implements XiYiJi{
public void xiyifu() {
System.out.println("美的洗衣机洗衣服");
}
}
这两段代码定义了两个类,该类实现了XiYiJi接口中的xiyifu方法。xiyifu方法的功能是在控制台输出内容。
工厂类:
public class HaiErFactory implements IFactory{
public BingXiang createBingXiang() {
return new HaiErBingXiang();
}
public XiYiJi createXiYiJi() {
return new HaiErXiyiji();
}
}
public class MeiDiFactory implements IFactory {
public BingXiang createBingXiang() {
return new MeiDiXiang();
}
public XiYiJi createXiYiJi() {
return new MeiDiXiyiji();
}
}
两段代码定义了一个名为两类,实现了IFactory接口。该类提供了两个方法:createBingXiang和createXiYiJi,分别用于创建BingXiang和XiYiJi对象。这两个方法的返回值类型分别是BingXiang和XiYiJi。通过这个工厂类,可以方便地获取特定类型的对象,而不需要知道具体的实现类。
测试类:
public class Test {
public static void main(String[] args) {
IFactory factory = new MeiDiFactory();
BingXiang bingXiang = factory.createBingXiang();
bingXiang.baoxian();
XiYiJi xiYiJi = factory.createXiYiJi();
xiYiJi.xiyifu();
}
}
首先,创建了一个MeiDiFactory对象作为工厂,该工厂负责创建BingXiang和XiYiJi对象。
然后,通过factory.createBingXiang()方法创建了一个BingXiang对象,并调用其baoxian()方法。
接着,通过factory.createXiYiJi()方法创建了一个XiYiJi对象,并调用其xiyifu()方法。
该代码的主要作用是通过工厂模式来创建不同类型的对象,并调用它们各自的方法。
5.4 工厂模式小结:
1、工厂方法模式与抽象工厂模式的区别在于:
(1)工厂方法只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。
(2)抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例
6. 克隆模式
克隆模式: 也称之为 原型模式,顾名思义,就是创建一个实例对象的副本。
实现克隆:
实体类:
public class Dog implements Cloneable{
private String name;
private Integer age;
private String color;
private Dog friend;
//该函数是一个clone方法的实现,用于创建当前对象的一个浅拷贝。首先,通过调用super.clone()方法创建一个原始对象的拷贝,然后将其强制转换为Dog类型.
public Dog clone() throws CloneNotSupportedException {
Dog clone =(Dog) super.clone();
return clone;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", friend=" + friend +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Dog getFriend() {
return friend;
}
public void setFriend(Dog friend) {
this.friend = friend;
}
}
实体类:
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.setName("tom");
d.setAge(1);
d.setColor("黑白");
Dog dog = new Dog();
dog.setName("jerry");
d.setFriend(dog);// jerry是tom的朋友
// System.out.println(d);
//
try {
Dog clone = d.clone();
// System.out.println(clone);
clone.getFriend().setName("jjjjj");
System.out.println(clone);
System.out.println(d);
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
这段代码主要演示了对象的克隆操作。首先创建了一个Dog对象d并设置了其属性,然后又创建了另一个Dog对象dog并将其设置为d的朋友。接着使用clone()方法对d进行克隆,得到一个与d属性相同的clone对象。然后修改clone对象的朋友的名字,输出clone对象和原对象d,可以看到修改只对clone对象生效,原对象d并未受到影响。