1、设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
2、设计模式不是一种方法和技术,而是一种思想
3、设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用
4、学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成
1、名字: 必须有一个简单,有意义的名字
2、问题 :描述在何时使用模式
3、解决方案: 描述设计的组成部分以及如何解决问题
4、效果: 描述模式的效果以及优缺点
1、创建型模式 :对象的创建。
简单工厂模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式。(6个)
2、结构型模式 :对象的组成(结构)
外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。(7个)
3、行为型模式 :对象的行为
模版方法模式、观察者模式、状态模式、职责链模式、命令模式、访问者模式、策略模式、备忘录模式、迭代器模式、解释器模式。(10个)
简单工厂模式:又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些对实现了同一接口类的实例
优点:客户端不需要在负责对象的创建,从而明确了各个类的职责
缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
举个例子:
Animal抽象类,作为父类
public abstract class Animal {
public abstract void eat();
}
Cat类继承Animal
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
Dog类继承Animal
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
AnimalFactory工厂类:专门用来创建对象的实例
public class AnimalFacrtory {
//私有的构造方法,说明不能通过new来创建这个类的实例,因此要调用这个类中的方法,这些方法必须是静态的才能被调用。
private AnimalFacrtory(){
}
public static Animal createAnimal(String type){
if("dog".equals(type)){
return new Dog();
}else if("cat".equals(type)){
return new Cat();
}else{
return null;
}
}
}
测试类:
public class AnimalDemo {
public static void main(String[] args) {
//创建一个Dog类的实例对象
Animal a = (Dog) AnimalFacrtory.createAnimal("dog");
a.eat();
//创建一个Cat类的实例对象
a = AnimalFacrtory.createAnimal("cat");
a.eat();
//创建一个Pig类的实例对象
a = AnimalFacrtory.createAnimal("pig");
if(a != null){
a.eat();
}else{
System.out.println("工厂并未提供这个类的实例");
}
}
}
工厂方法模式:工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
缺点:需要额外的编写代码,增加了工作量
Animal抽象类,作为父类
public abstract class Animal {
public abstract void eat();
}
Factory:工厂接口,不同的对象分别会创建一个对应的工厂(在里面实现这个对象的创建)然后继承这个接口
public interface Factory {
public abstract Animal createAnimal();
}
Cat类继承Animal
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
CatFactory:专门用来创建Cat对象的工厂类
//专门造Cat的工厂
public class CatFactory implements Factory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
Dog类继承Animal
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
DogFactory:专门来创建狗对象实例的工厂类
//专门造Dog的工厂
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
测试类:不同的对象就调用对应的对象工厂
public class AnimalDemo {
public static void main(String[] args) {
//造狗
Factory f = new DogFactory();
Animal a = f.createAnimal();
a.eat();
//造猫
f = new CatFactory();
a = f.createAnimal();
a.eat();
}
}
单例设计模式:
饿汉式:类一加载就创建对象。
懒汉式:用的时候才去创建对象
单例模式的思想是什么?
保证类在内存中只有一个。
饿汉式:是不会出问题的单例模式。也叫立即加载,在get之前就创建好了实例。
懒汉式:可能会出问题的单例模式。也叫延迟加载,在get的时候才创建实例。
懒加载(延迟加载)
线程安全问题(因此加synchronized以防线程安全问题)
模拟懒汉式可能出现的问题:因为student是共享数据,可能会引起并发的访问getInstance()方法。当多个线程访问时,一个线程A进来后,可能调用sleep方法在这里沉睡;一个B线程进来的时候B也要去沉睡(假设此时A在沉睡还没醒,也就是A,B都在睡);当A线程醒来后,就会new一个对象,当B线程醒来后,也会new一个对象。这就不符合单例模式的特点了。
Student类:在多线程操作中就会出现错误。
public static Student getInstance() {
try {
if (student == null) {
Thread.sleep(1000); // 模拟延迟加载
student = new Student();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return student;
}
线程类:
public class ThreadA extends Thread {
@Override
public void run() {
//输出每次创建实例的哈希值。如果是单利模式,哈希值肯定是相同的
System.out.println(Student.getInstance().hashCode());
}
}
测试类:
public class StudentDemo {
public static void main(String[] args) {
ThreadA a = new ThreadA();
ThreadA a1 = new ThreadA();
ThreadA a2 = new ThreadA();
a.start();
a1.start();
a2.start();
}
}
输出:输出的哈希值并不相同,出现多实例的情况,不是单利模式该出现的结果。
1826043875
630607848
794303391
解决方法:
一:在方法上声明synchronized关键字(效率低)
public class Student {
private static Student student = null;
private Student() {
}
//在方法上使用synchronized
public synchronized static Student getInstance() {
try {
if (student == null) {
Thread.sleep(1000); // 模拟延迟加载
student = new Student();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return student;
}
}
二:同步代码块(效率低)
public class Student {
private static Student student = null;
private Student(){
}
public static Student getInstance(){
try {
//使用同步代码块
//非静态的同步函数对象是this,静态的同步函数对象是:字节码对象。即类.class
synchronized (Student.class) {
if(student == null){
Thread.sleep(1000); //模拟延迟加载
student = new Student();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return student;
}
}
三:使用双重判断DCL(Double check Locking双检查锁机制)(推荐)
当student == nul的时候,A线程进来了,它加了一下锁只有,才进入了第二个if(student == null);然后睡觉;
这个时候B线程也进来了,B通过了第一个if(student == null),当B向下执行的时候,进不去了遇到了synchronized,发现这里被A加了锁,没有办法,B就等着,等A解锁。
此时A解锁了,它new了一个对象之后,继续往下执行,然后把锁解开了,这里的student已经不是null了。
B发现A解锁了,他继续往下执行,发现student不等于null了,于是他直接返回了A创建的那个对象student。
public class Student {
private static Student student = null;
private Student() {
}
//在方法上使用synchronized
public static Student getInstance() {
try {
if (student == null) {
synchronized (Student.class) { //当B线程来的时候,需要等着,等A线程解锁后才让进去
if(student == null){ //A线程
Thread.sleep(1000); // 模拟延迟加载
student = new Student();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return student;
}
}
单例设计模式:单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
缺点:没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责
如何保证类在内存中只有一个对象呢?
1、把构造方法设置为私有方法
2、在成员位置自己创建一个对象(也必须是私有,为了不让外界去修改)
3、通过一个公共的方法提供访问(这个方法必须设置为静态,外界才能访问)
Student类:
public class Student {
//在成员位置自己创建一个对象(为了不让外界直接访问或者修改这个值,要加private)
//饿汉式:类一加载就去创建对象
private static Student student = new Student();
//把构造设置为私有
private Student(){
}
//通过一个公共的方法进行访问(为了让外界能够直接访问该方法,创建成一个静态方法)
public static Student getInstance(){
return student;
}
}
测试类:Student类在内存中只有一个对象
public class StudentDemo {
public static void main(String[] args) {
Student student = Student.getInstance();
Student student2 = Student.getInstance();
System.out.println(student == student2); //true
}
}
Student类:
public class Student {
//在成员位置自己创建一个对象(为了不让外界直接访问或者修改这个值,要加private)
private static Student student = null;
//把构造设置为私有
private Student(){
}
//通过一个公共的方法进行访问(为了让外界能够直接访问该方法,创建成一个静态方法)
//懒汉模式,使用的时候才去创建对象
public static Student getInstance(){
if(student == null){
student = new Student();
}
return student;
}
}
测试类:加synchronized防止出现线程不安全
public class StudentDemo {
public synchronized static void main(String[] args) {
Student student = Student.getInstance();
Student student2 = Student.getInstance();
System.out.println(student == student2); //true
}
}
Runtime:每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行实例。
public Process exec(String command):在单独的进程中执行指定的字符串命令。
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
Runtime r = Runtime.getRuntime();
r.exec("notepad"); //打开记事本
r.exec("calc"); //打开计算器
}
}
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法就是提供了一个很好的代码复用平台。通过把不变行为搬移到超类,去除子类中复用代码体现它的优势。
举例:
比如说一个复杂的任务,公司中的大牛们将主要逻辑写好,然后把那些看上去比较简单的方法写成抽象的,然后交给其他同事去开发。
模板方法的结构:
由一个抽象类和一个(或一组)实现类通过集成结构组成。
抽象类的方法分为3中:
(1)抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
(2)模板方法:由抽象类声明并加以实现。一般来说模板方法调用抽象方法来完成主要的逻辑功能,而且,模板方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
(3)钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模板方法的逻辑。
钩子方法的引入使得子类可以控制父类的行为。
最简单的钩子方法就是空方法,也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。
比较复杂一点的钩子方法可以对其他方法进行约束,这种钩子方法通常返回一个boolean类型,即返回true或false,用来判断是否执行某一个基本方法。由子类来决定是否调用hook方法。