设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式代表了最佳的实践。同时也提高了软件的维护性、通用性和扩展性、并降低软件的复杂度。
一共有三种类型的设计模式,共有23种:
创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
结构性模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter)、状态模式、策略模式、职责链模式(责任链模式)。
重点的有单例模式、简单工厂、装饰模式、模板模式、适配器模式、代理模式(静态代理和动态代理)。
注意:模式只是思路,并没有标准的编码规则。
此外,设计模式必须遵守的七大原则(即在编程时应遵守的原则):单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特原则、合成复用原则。
设计原则核心思想:
1、找出应用中可能需要变化之处,并独立出来,不要和不需要变化的代码混在一起。
2、针对接口编程,而不是针对实现编程。
3、为了交互对象之间的松耦合设计。
下面就比较重要的设计模式进行展开。
单例模式
采取一定的方法保证在整个系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
//将构造函数私有化,类的外部不能再实例对象。
//正常使用的就是多例模式,单例是所有的对象共用一个空间
public class Student {
String name;
//静态常量
static Student instance=new Student();
private Student(){
}
//public Student(){
//
//}
}
饿汉式
//将构造函数私有化,类的外部不能再实例对象。
//私有化静态属性,只提供get方法
//饿汉模式使用的是静态代码块的方式,实现了lazy loading(懒加载:在使用的时候再去创建对象)
public class Student1 {
String name;
//缺点:无论是否使用,都先创建好了空间
//private final static Student1 instance=new Student1();
/*优点:调用的时候再创建空间,只会创建一次,因为static代码块只会执行一次
*缺点:如果空间很大,创建时间较慢,那么第一个调用者需要等待
* */
private final static Student1 instance;
//静态代码块
static {
instance=new Student1();
}
private Student1(){
}
public static Student1 getInstance(){
return instance;
}
}
懒汉式
//懒汉模式(不推荐),在get方法中提供判断来创建对象,实现了lazy loading,在方法中创建对象,会
有线程安全问题,可以使用同步锁来解决问题,效率变低
//可以使用double check(双重检查机制),搭配volatile关键字,可以提高懒汉模式的多线程安全及效率
问题
public class Student2 {
String name;
private volatile static Student2 instance=null;
private Student2(){
}
//非线程安全,不推荐
//public static Student2 getInstance(){
// if (instance==null)instance=new Student2();
// return instance;
//}
/* 线程安全、同步方法,但不推荐(效率低)
public static synchronized Student2 getInstance(){
if (instance==null)instance=new Student2();
return instance;
}*/
//双重检查机制
public static synchronized Student2 getInstance(){
//第一次检查是保证只对第一次创建对象加锁
if (instance==null){
//加锁
synchronized (Student2.class){
//防止线程同步
if (instance==null){
instance=new Student2();
}
}
}
return instance;
}
}
静态内部类
//静态内部类实现单例,可以lazy loading,同时jvm帮助解决线程安全问题
public class Student3 {
//私有化构造函数
private Student3(){
}
//创建一个静态内部类
private static class innerClass{
//在内部类中,创建内部类
private static Student3 instance=new Student3();
}
//提供一个公共的get方法
public static Student3 getInstance(){
return innerClass.instance;
}
}
枚举实现单例模式
//枚举实现单例,对象加载问题,线程问题jvm都帮我们解决
public enum Student4 {
instance;
public String name;
public void hello(){
System.out.println("hello");
}
}
测试代码块:
Student s1 = Student.instance;
Student s2 = Student.instance;
System.out.println(s1);
System.out.println(s2);
s1.name = "A";
s2.name = "B";
System.out.println(s1.name);
System.out.println(s2.name);
// 运行会报错
Student.instance = (Student) new Object();
System.out.println("-------------Enum-----------------");
System.out.println(Student4.instance.name);
Student4.instance.name = "1";
Student4.instance.name = "2";
System.out.println(Student4.instance.name);
简单工厂模式
简单工厂模式属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例,也是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
public interface Pizza {
public void pizzaType();
}
public class GreekPizza implements Pizza{
@Override
public void pizzaType() {
System.out.println("希腊披萨");
}
}
public class CheekPizza implements Pizza{
@Override
public void pizzaType() {
System.out.println("芝士披萨");
}
}
//简单工厂模式的核心,负责实现创建所有实例的内部逻辑,工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象
public class PizzaFactory {
public static Pizza createPizza(String type){
Pizza pizza=null;
if (type.equals("greek")){
pizza=new GreekPizza();
}else if (type.equals("cheek")){
pizza=new CheekPizza();
}
return pizza;
}
}
//测试代码块
public static void main(String[] args) {
Pizza pizza=new GreekPizza();
pizza.pizzaType();
Pizza pizza1=PizzaFactory.createPizza("cheek");
pizza1.pizzaType();
}
优点:外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费"对象就可以了,不必管这些对象究竟是如何创建及组织的,有利于整个结构的优化。
缺点:违反了高内聚责任分配原则,将全部创建逻辑集中到一个工厂类中,他所能创建的类只能是事先考虑的,如果需要添加新的类,就需要改变工厂类了。当系统中产品不断增多时,可能需求也繁杂,很难避免模块功能的蔓延,对系统的维护和扩展非常不利。
装饰者模式
指的是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,他是通过创建一个包装对象,也就是装饰来包裹真实的对象。设计原则:1、多用组合,少用继承。2、类应设计的对扩展开放,对修改关闭。
适用性:
1、需要扩展一个类的功能,或给一个类添加附加责任。
2、需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4、当不能采用生成子类的方法进行扩充时,一种情况是可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长,另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
I/O流就是装饰者模式。
//有一个共同的类、一个实际干活的类、一个增强能力的类,在增强能力的类中有一个实际干活的类
public interface Job {
public void work();
}
public class Emp2 implements Job{
@Override
public void work() {
System.out.println("开开心心摸鱼");
}
}
public class Emp1 implements Job{
@Override
public void work() {
System.out.println("勤勤恳恳工作");
}
}
public class Manager implements Job{
private Job jtype;
public Manager(Job jtype){
this.jtype=jtype;
}
@Override
public void work() {
System.out.println("发布任务");
jtype.work();
System.out.println("总结归纳");
}
}
//测试代码块
public static void main(String[] args) {
Job emp1=new Emp1();
Job emp2=new Emp2();
Manager manager=new Manager(emp1);
Manager manager1=new Manager(emp2);
manager.work();
System.out.println("------------------------");
manager1.work();
}
模板模式
允许一个抽象类中定义一个操作的方法的骨架,将具体的步骤延迟到子类中。所谓模板就是大体框架固定,内在细节由用户定制。
适用于:
当多个子类具有公用的方法,但执行流程逻辑相同时;重要的、复杂的方法可以考虑。
优点:
1、便于子类继续扩展。2、便于代码复用。3、子类可以通过扩展方式增加相应的功能,符合开闭原则。
//父类必须是一个抽象类
//缺点:由于是单继承,所以可扩展性不强
//JDK1.8之后可以使用接口充当父类,在接口使用default提供方法体
public interface Beike {
default void all(){
beike();
shangke();
xiake();
}
default void beike(){
System.out.println("备课");
}
void shangke();
default void xiake(){
System.out.println("下课");
}
}
abstract class Beike1 {
public final void all(){
beike();
shangke();
xiake();
}
public void beike(){
System.out.println("备课");
}
public abstract void shangke();
public void xiake(){
System.out.println("下课");
}
}
public class Chinese extends Beike1 implements Beike{
@Override
public void shangke() {
System.out.println("语文课");
}
}
public class English extends Beike1 implements Beike{
@Override
public void shangke() {
System.out.println("英语课");
}
}
//测试代码块
public static void main(String[] args) {
Beike b1=new Chinese();
b1.all();
System.out.println("--------------------------");
Beike1 b2=new English();
b2.all();
}
代理模式
静态代理
允许在程序运行前就已经确定代理对象和目标对象的关系。在这种模式下,代理类和目标类实现相同的接口,代理类持有目标对象,并在方法调用前后进行额外的操作。
适用于:
业务功能固定不变的情况,可以有效的隔离目标对象和用户,提供统一的接口,保护目标对象,并可以方便进行扩展和增强。
public interface ZuFang {
void zufang();
}
public class FangDong implements ZuFang{
@Override
public void zufang() {
System.out.println("房东直租1200元/月");
}
}
public class FangDongProxy implements ZuFang{
private ZuFang zf;
public FangDongProxy(ZuFang zf){
this.zf=zf;
}
@Override
public void zufang() {
System.out.println("加租200元");
zf.zufang();
System.out.println("有问题找我");
}
}
//测试代码块
public static void main(String[] args) {
FangDongProxy proxy=new FangDongProxy(new FangDong());
proxy.zufang();
}
动态代理
在运行时动态生成代理对象的技术,它允许在不修改源代码的情况下为原有的类提供额外的功能或控制访问。
主要通过两个核心类实现:Proxy类:用于动态创建代理类
InvocationHandler接口:用于处理代理对象的方法调用
特点:动态代理具有灵活性和可扩展性,可以在运行时创建不同类型的代理对象,无需事先知道具体的被代理类。
主要应用于:AOP(面向切面编程)、远程方法调用(RPC)、消息中间件、数据库连接池、缓存、安全控制,还有JUnit单元测试就是使用动态代理完成的。
面向切面编程:实现横切关注点,如日志记录,性能监控,事务管理。
远程方法调用:将远程方法调用封装为本地方法调用,简化远程通信的操作。
消息中间件:用于消息中间件的发布、订阅模型。
public interface ZuFang {
void zufang();
}
public class FangDong implements ZuFang{
@Override
public void zufang() {
System.out.println("房东直租");
}
}
public interface Teach {
void teach();
}
public class Teacher implements Teach{
@Override
public void teach() {
System.out.println("老师上课");
}
}
//代理工程
public class ProxyFactory {
//目标对象
private Object o=null;
public ProxyFactory(Object o){
this.o=o;
}
//代理对象
public Object getProxy(){
Object proxy=null;
/*参数
* 1、目标对象的类加载器
* 2、目标对象的接口
* 3、控制器,会帮助我们找到目标类的父类,然后帮我们创建一个子类,实现这个父类接口,并且
重写这个父类的方法
* */
proxy= Proxy.newProxyInstance(
o.getClass().getClassLoader(),
o.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
//System.out.println(proxy);
System.out.println("动态代理加强");
Object result=method.invoke(o,args);//目标对象
System.out.println("多收钱");
return result;
}
}
);
return proxy;
}
}
//测试代码块
public static void main(String[] args) {
ProxyFactory proxyFactory=new ProxyFactory(new FangDong());
ZuFang zuFang=(ZuFang)proxyFactory.getProxy();
zuFang.zufang();
System.out.println(zuFang.getClass());
System.out.println("--------------------");
ProxyFactory teacherProxy=new ProxyFactory(new Teacher());
Teach t=(Teach)teacherProxy.getProxy();
t.teach();
System.out.println(t.getClass());
}
最后扩展一个小的知识点:
UML类图:
Unified modeling language ,简称UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果。UML本身是一套符号的规定,就像数学符号和化学符号一样,用于描述软件模型中的各个元素和他们之间的关系。
类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合。
聚合关系:表示的是整体和部分的关系,整体和部分可以分开,聚合关系是关联关系的特例,所以它具有关联的导航性与多重性,
组合关系:整体和部分的关系,但是整体和部分不能分开。例如Person和IDcard、Head:Head和Person就是组合,IDcard和Person就是聚合。