JAVA设计模式(Design Pattern)

Creational Design Pattern

01 - 单例模式(Singleton Pattern)

单例模式介绍

-Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
单例是一种创造性的设计模式,它确保同类中只有一个对象存在,并为任何其他代码提供对它的单一访问点。
-The keystone in the pattern is a private constructor
模式中的keystone是一个私有构造函数
public class MyClass {
    
    // 静态变量,初始值为null (Java)
    private static final MyClass unique; 
    // initialize the reference in the static block
    static { unique = new MyClass(); } 

    // 私有构造函数,因此没有其他类可以实例化该类
     private MyClass () { // disable: MyClass obj = new MyClass(); ... 
     }

    // 私有复制构造函数,因此没有其他类可以复制该类 
    private MyClass (MyClass other) { // disable: MyClass objA = new MyClass(objB);
    // ... 
    }

    // 静态方法返回唯一的引用
    public static MyClass getInstance() {
        return unique; 
     }
}
Example:
Testing should show the same memory address for two MyClass instances which are actually referencing the same object on heap:
测试应该显示两个MyClass实例的相同内存地址,这两个实例实际上引用了堆上的相同对象:
public class TestClass {
 public static void main (String[] args){
 MyClass mc = MyClass.getInstance();
 System.out.println("Memory address:" + mc); //MyClass@e86f41
 MyClass mc2 = MyClass.getInstance();
 System.out.println("Memory address:" + mc2); //MyClass@e86f41
 }
}

单例模式的优点:

Lazy initialization (the tactic of delaying the creation of an object, the
calculation of a value, or some other expensive process until the first time
it is needed)
This is useful if initiation is a heavy process (e.g., creating a database
connection)

缺点: 

Some consider this an anti-pattern .
Can complicate unit testing. Why? You cannot just use a class that
depends on Singleton in some other context . You’ll have to carry the
Singleton class as well.

 饿汉式:

单例设计模式 - 饿汉式

可以确保获得的对象唯一

----------------------------------------------
// 饿汉式-单例设计模式
 class Single1{

     // 1. 私有该类构造方法
     private Single1(){
     }

     // 2.在类中自己创建对象
     public static Single1 s = new Single1();

     // 3.提供获取对象的方法--getIntance()
     public static Single1 getInstance(){
         return s;
     }
 }
// 饿汉式-单例设计模式
class Single1{

    // 1. 私有该类构造方法
    private Single1(){
    }

    // 2.在类中自己创建对象
    public static Single1 s = new Single1();

    // 3.提供获取对象的方法--getIntance()
    public static Single1 getInstance(){
        return s;
    }
}

懒汉式:

懒汉式-单例设计模式 (延迟加载模式)

不能确保获取对象的唯一性, 需要加上判断条件

-----------------------------------------------
class Single2{
     // 1.先私有构造方法
     private Single2(){
     }

     // 2.不着急创建对象
     public static Single2 s;

     // 3.在被获取对象时创造对象
     public static Single2 getInstance(){
         if(s == null){
         s = new Single2();
         }
     return s;
     }
 }

弊端:在多线程并发操作的时候,有可能创建出多个对象

-----------------------------------------------

 class Single2{

     // 1.先私有构造方法
     private Single2(){
     }

     // 2.不着急创建对象
     public static Single2 s;

     // 3.在被获取对象时创造对象
     public static Single2 getInstance(){
         synchronized (Single2.class){
             if(s == null){
                 s = new Single2();
             }
         }
         return s;
     }
 }

 弊端:效率非常低!
 ------------------------------------------------
 
 class Single2{
 
     // 1.先私有构造方法
     private Single2(){
     }
 
     // 2.不着急创建对象
     public static Single2 s;
 
     // 3.在被获取对象时创造对象
     public static Single2 getInstance(){
         if(s == null){
             synchronized (Single2.class){
                 if(s == null){
                     s = new Single2();
                 }
             }
         }
         return s;
     }
 }
class Single2{

    // 1.先私有构造方法
    private Single2(){
    }

    // 2.不着急创建对象
    public static Single2 s;

    // 3.在被获取对象时创造对象
    public static Single2 getInstance(){
        if(s == null){
            synchronized (Single2.class){
                if(s == null){
                    s = new Single2();
                }
            }
        }
        return s;
    }
}

并发线程执行的不确定性

(Non-deterministic Nature of Concurrent Thread Execution)

为什么会出现两次不同的地址?

A:当 thread1thread2 同时执行时,它们都调用 getInstance() 方法。这可能会发生在以下情况下:

  • 第一次调用:
    • 假设 thread1 先运行并检查 unique 是否为 null,发现是 null,然后创建了一个新的 MyClass 实例。
  • 线程切换:
    • thread1 完成实例化之前,操作系统可能会切换到 thread2。如果 thread2 此时也检查到 unique 仍然是 null,它也会尝试创建另一个实例。

这种情况下,即使 MyClass 设计为单例,它的实现不安全也会导致产生两个不同的实例。

public class TestClass extends Thread {
 public static void main (String[] args){
 TestClass thread1 = new TestClass();
 TestClass thread2 = new TestClass();
 thread1.start();
 thread2.start();
 while (thread1.isAlive() || thread2.isAlive()) {
 // Main process is required to wait for the termination of thread1 and thread2
 // System.out.println("Waiting...");
 }
 }
 public void run(){
 MyClass mc = MyClass.getInstance();
 System.out.println("Memory address:" + mc);
 }
}

/*
    Possible Outputs:

    Memory address: MyClass@e86f41
    Memory address: MyClass@e86f41

    Memory address: MyClass@e86f42
    Memory address: MyClass@e86f43
*/

另一个例子:

下面的代码可能会输出多次不同的结果

1. 线程调度

Java 的线程调度是由操作系统管理的,线程的执行顺序并不是固定的。每次运行程序时,操作系统可能会以不同的顺序调度 thread1thread2thread3,这会导致输出的顺序不同。例如:

  • 在某些运行中,thread1 可能会先执行,而在另一些运行中,thread3 可能会先执行。

2. 并发输出

每个线程在 run() 方法中循环三次输出信息。由于线程是并发执行的,多个线程可能会在几乎相同的时间点输出信息。这使得输出的顺序和组合具有不确定性。

public class TestClass extends Thread {
    private int param; // 用于存储线程参数

    public TestClass(int param) { 
        this.param = param; 
    }

    public static void main(String[] args){
        TestClass thread1 = new TestClass(1);
        TestClass thread2 = new TestClass(2);
        TestClass thread3 = new TestClass(3);
        thread1.start(); // 启动线程1
        thread2.start(); // 启动线程2
        thread3.start(); // 启动线程3
        
        while (thread1.isAlive() || thread2.isAlive() || thread3.isAlive()) {
            // 主线程等待所有子线程完成
        }
    }

    public void run(){
        for (int i = 0; i < 3; i++)
            System.out.println("This is thread " + param); // 输出线程信息
    }
}

02 - 进程(Process)与线程(Threads)

进程(Process):

是计算机中的程序关于某数据集合上的一次运行活动是系统进行资源分配的基本单位

简单理解:程序的执行过程

        1.独立性:每一个进程都有自己的空间,在没有经过进程本身允许的情况下,一个进程不可以直接访问其它的的进程空间

        2.动态性:进程是动态产生,动态消亡的
        3.并发性:任何进程都可以同其它进程一起并发执行

并行和并发


并行: 在同一时刻,有多个指令在多个CPU上【同时】执行
并发: 在同一时刻,有多个指令在单个CPU上【交替】执行

线程介绍:

进程可以同时执行多个任务,每个任务就是线程

Threads within a process share
1. 文本段(程序代码) Text segment (program code)
2. 数据段  Data segment
3. 操作系统资源(例如,打开的文件和信号) OS resources (e.g., opened files and signals).
● Each thread has its own program counter , register set , and a stack space .
Multithreading is the ability of a system to execute the different parts, called threads, of a program simultaneously.
        Individual threads share the same memory and data variables

使用线程的好处:

  • 快速的上下文切换 

    • 所有线程运行在相同的地址空间(代码 + 数据),因此它们之间的上下文切换比进程之间的切换更快。
  • 适合后台处理

    • 一些不需要用户交互的服务更适合在后台或守护线程中运行。
  • 并行处理以加速执行

    • 允许程序利用机器上的多个 CPU(核心);线程可以在多个处理器或核心上并行运行。
  • 异步处理

    • 线程以简单的方式允许重叠的 I/O 和计算(即使在单处理器机器上),以隐藏 I/O 延迟。
    • 这使得程序更加响应迅速,对于服务器应用程序同时处理多个客户端非常重要。

线程使用的三种方法:

1. 要在Java中使用线程,一种方法是继承类线程。

        -1.编写一个类继承Thread

        -2.重写run方法

        -3.将线程任务代码写在run方法中

        -4.创建线程对象

        -5.调用start方法开启线程

注:只有调用了start方法才是开启了新的线程

当调用.start()开启线程,线程将自动开始运行run()方法。检查子线程是否完成

其中,run方法是子类在继承时必须复写的方法。

public class TestClass extends Thread {
    public static void main (String[] args){
        System.out.println("A message from main.");
        TestClass thread = new TestClass();
        thread.start();
        while (thread.isAlive()) {
            System.out.println("Waiting...");
        }
    }
    public void run(){
        System.out.println("A message from thread.");
    }
}

/*
A message from main.
Waiting...
Waiting...
Waiting...
A message from thread.
Waiting...
*/

java程序默认多线程,程序启动后默认出现两条线程

1.主线程

2.垃圾清理线程

2.创建线程的另一种方法是声明一个实现Runnable接口的类。(当类已经继承一个父类时)

1.编写一个类实现Runnable接口
2.重写run方法
3.将线程任务代码写在run方法中
4.创建线程任务资源
5.创建线程对象,将资源传入
6.使用线程对象调用start方法,开启线程

然后,该类实现run方法。然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

public class ThreadDemo3 {
    /*
        创建线程的另一种方法是声明一个实现Runnable接口的类。

            1.编写一个类实现Runnable接口
            2.重写run方法
            3.将线程任务代码写在run方法中
            4.创建线程任务资源
            5.创建线程对象,将资源传入
            6.使用线程对象调用start方法,开启线程
     */
    public static void main(String[] args) {
        // 4.创建线程任务资源
        myrunnable mr = new myrunnable();
        // 5.创建线程对象,将资源传入
        Thread t = new Thread(mr);
        // 6.使用线程对象调用start方法,开启线程
        t.start();

        // 主线程与创建的线程多线程同时执行
        for(int i = 0; i <=2000; i++){
            System.out.println("main");
        }
    }
}
// 1.编写一个类实现Runnable接口
class myrunnable implements Runnable{
    // 2.重写run方法
    @Override
    public void run() {
        // 3.将线程任务代码写在run方法中
        for(int i = 0; i <=200; i++){
            System.out.println("run for" + i);
        }
    }
}

3.开启线程的第三种方式:实现Callable接口
        1.编写一个类实现Callable接口
        2.重写call方法
        3.将线程任务代码写在call方法中

        4.创建线程任务资源对象
        5.创建线程任务对象,封装线程资源
        6.创建线程对象,传入线程任务
        7.使用线程对象调用start开启线程

public class ThreadDemo4 {
    /*\
        3.开启线程的第三种方式:实现Callable接口
            1.编写一个类实现Callable接口
            2.重写call方法
            3.将线程任务代码写在call方法中
            4.创建线程任务资源对象
            5.创建线程任务对象,封装线程资源
            6.创建线程对象,传入线程任务
            7.使用线程对象调用start开启线程
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建线程任务资源
        mycallable mc = new mycallable();
        // 创建线程任务对象,封装线程资源
        FutureTask<Integer> task1 = new FutureTask<>(mc);
        FutureTask<Integer> task2 = new FutureTask<>(mc);
        // 创建线程对象,传入线程任务
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);

        t1.start();
        t2.start();

        // 获取callable结果需要等线程执行完毕
        Integer result1 = task1.get();
        Integer result2 = task2.get();
        System.out.println(result1);
        System.out.println(result2);


    }
}
// 1.编写一个类实现Callable接口
class mycallable implements Callable<Integer>{
    // 2.重写call方法
    @Override
    public Integer call() throws Exception {
        // 3.将线程任务代码写在call方法中
        int sum = 0;
        for(int i = 0; i <=100; i++){
            sum += i;
            System.out.println("sum = " + sum);
        }
        return sum;
    }
}

线程运行过程(为什么while循环会终止):

// Another demo class for Java Runnable interface
public class TestClass implements Runnable {
    public static void main (String[] args){
        System.out.println("A message from main.");
        TestClass obj = new TestClass();
        Thread thread = new Thread(obj);
        thread.start();
        while (thread.isAlive()) {
            System.out.println("Waiting...");
        }
    }
    public void run(){
        System.out.println("A message from thread.");
    }
}

运行过程:
1.启动线程:
        当 thread.start() 被调用时,新的线程开始执行 run() 方法。
        此时,主线程(main 方法中的线程)继续执行后面的代码。
        

        while 循环:
        while (thread.isAlive()) 检查 thread 是否仍然在运行。如果 thread 仍在运行,循环会继续出         "Waiting..."。


2.线程结束:
        当 run() 方法中的代码执行完成后,线程会终止,isAlive() 将返回 false。
        此时,while 循环会停止,因为条件不再满足。

3.具体原因:
        不定次数的输出:
                while 循环的运行次数取决于 thread 的执行时间。在 run() 方法中,输出 "A message from thread." 的操作是非常快的,因此 while 循环可能会输出多次 "Waiting...",直到新线程完成执行。
        线程调度:
        由于线程调度的不可预测性,主线程可能会在新线程输出消息之前多次循环输出"Waiting..."。

线程相关方法:

线程的问题:

  1. 在许多情况下,并发线程共享相同的数据段资源。
  2. 它们的并发读写可能导致内存不一致,即数据丢失或损坏。

示例:

1.两个线程都使用相同的布尔变量。

2.他们也有同样的优先级(设置优先级可以改变线程运行)。

3.线程1将检查该值(F)。

4.几毫秒后,线程2将更改该值(T)。

5.在此之后,线程1立即会根据不准确/过时的数据执行一些操作(F)。

线程安全和同步



安全问题出现的条件:
        是多线程环境
        有共亨数据
        有多条语句操作共享数据

同步技术:
        将多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程可以执行


        1. 同步代码块:

锁对象可以是任意对象,但是需要保证多条线程的锁对象,是同一把锁


同步可以解决多线程的数据安全问题,但是也会降低程序的运行效率

在线程进入同步代码块后,其它线程无法进入执行该代码

Case1: 继承Runnable 接口

public class ThreadTest1 {
    /*
        需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
            -多条线程共同操作同一份资源则可能会出问题

            同步代码块:
                synchronized(锁对象){
                多条语句操作共享数据的代码}
     */
    public static void main(String[] args) {


        TicketTask tt = new TicketTask();

        Thread t1 = new Thread(tt,"counter1");
        Thread t2 = new Thread(tt,"counter2");
        Thread t3 = new Thread(tt,"counter3");

        t1.start();
        t2.start();
        t3.start();

    }
}

class TicketTask implements Runnable{

    private Object o = new Object();

    private int tickets = 2000;

    public void run() {
        while(true){
            // 将代码上锁
            synchronized (o){
                if(tickets <= 0){
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "sells the " + tickets + "ticket");
                tickets--;

            }

        }
    }
}

Case2: 继承Thread类

public class ThreadTest3 {
    public static void main(String[] args) {
        TicketTask3 t1 = new TicketTask3();
        TicketTask3 t2 = new TicketTask3();
        TicketTask3 t3 = new TicketTask3();

        t1.setName("counter-1");
        t2.setName("counter-2");
        t3.setName("counter-3");

        t1.start();
        t2.start();
        t3.start();

    }

}

class TicketTask3 extends Thread {
    // 需要在锁对象和卖的票前加上static确保不改变
    // 因为在主方法中new了三次TicketTask2对象
    // 如果不加static将会出现三份锁对象以及ticket变量,导致出现重复买票

    private static int ticket = 2000;

    @Override
    public void run() {
        while (true) {
            // 上锁
            // 使用类对象的字节码对象(类.class)作为锁对象能保证唯一性和可读性
            synchronized (TicketTask3.class) {
                if (ticket == 0) {
                    break;
                }
                System.out.println(super.getName() + " sells " + ticket);
                ticket--;

            }

        }
    }
}

2. 同步方法

public class ThreadTest4 {
    public static void main(String[] args) {
        /*
            同步方法:在方法的返回值类型前面加入synchronized 关键字
            public synchronized void method(){}

            同步方法的锁对象:
                1.非静态的方法 : this
                2.静态的方法 : 类的字节码对象工
         */

        TicketTask4 tt4 = new TicketTask4();

        Thread t1 = new Thread(tt4, "t1");
        Thread t2 = new Thread(tt4, "t2");
        Thread t3 = new Thread(tt4, "t3");

        t1.start();
        t2.start();
        t3.start();

    }

}

class TicketTask4 implements Runnable {


    private int ticket = 2000;

    @Override
    public void run() {
        while (true) {
            // 上锁
            if (method()) {
                break;
            }
        }
    }

    public synchronized boolean method() {
        if (ticket == 0) {
            return true;
        } else {
            System.out.println(Thread.currentThread().getName() + "sells the " + ticket + "ticket");
            ticket--;
            return false;
        }
    }
}

3. lock 锁

public class ThreadTest5 {
    public static void main(String[] args) {


        TicketTask5 tt = new TicketTask5();

        Thread t1 = new Thread(tt,"counter1");
        Thread t2 = new Thread(tt,"counter2");
        Thread t3 = new Thread(tt,"counter3");

        t1.start();
        t2.start();
        t3.start();

    }
}

class TicketTask5 implements Runnable {

    private ReentrantLock lock = new ReentrantLock();

    private int tickets = 100;

    public void run() {
        while (true) {
            // 上锁
            try {
                lock.lock();
                if (tickets == 0) {

                    break;
                }
                System.out.println(Thread.currentThread().getName() + "sells the " + tickets + "ticket");
                tickets--;

            } finally {
                lock.unlock();

            }

        }

    }
}

死锁

线程通信

介绍:

1.确保线程能够按照预定的顺序执行并且能够安全地访问共享资源
2.使多条线程更好的进行协同工作

等待唤醒机制:

问题:sleep方法和wait方法的区别?
回答:

sleep方法是线程休眠,时间到了自动醒来,sleep方法在休眠的时候,不会释放锁

wait方法是线程等待,需要由其它线程进行notify唤醒,wait方法在等待期间,会释放锁

public class CorrespondenceDemo2 {
    /*
        三条线程通信
        问题:sleep方法和wait方法的区别?
        回答:
            sleep方法是线程休眠,时间到了自动醒来,sleep方法在休眠的时候,不会释放锁

            wait方法是线程等待,需要由其它线程进行notify唤醒,wait方法在等待期间,会释放锁
     */
    public static void main(String[] args) {
        Printer2 p = new Printer2();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    synchronized (Printer2.class) {
                        try {
                            p.printer1();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Printer2.class){
                        try {
                            p.printer2();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Printer2.class){
                        try {
                            p.printer3();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }
}

class Printer2{

    int flag = 1;



    public void printer1() throws InterruptedException {

        while( flag != 1){
            //
            Printer2.class.wait();
        }

        System.out.print("a");
        System.out.print("b");
        System.out.print("c");
        System.out.print("d");
        System.out.println();

        flag = 2;
        Printer2.class.notifyAll();
        // 唤醒线程2

    }

    public void printer2() throws InterruptedException {

        while( flag != 2){
            //
            Printer2.class.wait();
        }

        System.out.print("1");
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.println();

        flag = 3;
        Printer2.class.notifyAll();
        // 唤醒线程3

    }


    public void printer3() throws InterruptedException {
        while( flag != 3){
            //
            Printer2.class.wait();
        }

        System.out.print("!");
        System.out.print("@");
        System.out.print("#");
        System.out.print("$");
        System.out.print("%");
        System.out.println();

        flag = 1;
        Printer2.class.notifyAll();
        // 唤醒线程1
    }

}

更高效的等但唤醒机制:

当线程第一次调用await()函数时,自动绑定该condition对象

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class CorrespondenceDemo3 {
    /*
        三条线程通信
        问题:sleep方法和wait方法的区别?
        回答:
            sleep方法是线程休眠,时间到了自动醒来,sleep方法在休眠的时候,不会释放锁

            wait方法是线程等待,需要由其它线程进行notify唤醒,wait方法在等待期间,会释放锁
     */
    public static void main(String[] args) {
        Printer3 p = new Printer3();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        p.printer1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        p.printer2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        p.printer3();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

class Printer3{

    ReentrantLock lock = new ReentrantLock();

    Condition c1 = lock.newCondition();
    Condition c2 = lock.newCondition();
    Condition c3 = lock.newCondition();

    int flag = 1;



    public void printer1() throws InterruptedException {
        lock.lock();

        if(flag !=1){
            c1.await();

        }


        System.out.print("a");
        System.out.print("b");
        System.out.print("c");
        System.out.print("d");
        System.out.println();

        flag = 2;
        c2.signal();
        lock.unlock();
    }

    public void printer2() throws InterruptedException {
        lock.lock();
        if(flag !=2){
            c2.await();

        }

        System.out.print("1");
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.println();

        flag=3;
        c3.signal();
        lock.unlock();

    }


    public void printer3() throws InterruptedException {
        lock.lock();
        if(flag !=3){
            c3.await();
        }
        System.out.print("!");
        System.out.print("@");
        System.out.print("#");
        System.out.print("$");
        System.out.print("%");
        System.out.println();

        flag =1;
        c1.signal();
        lock.unlock();
    }

}

线程的竞争条件(Race Conditions & Data Race):

Race Conditions:

当两个或多个同时运行的线程访问一个共享数据项,最终结果取决于执行顺序时,我们有一个竞争条件。它是发生在事件的时间或顺序上的缺陷,导致错误的程序行为。

Race Conditions 不一定由 Data Race引起

A Race Condition without a Data Race:
class Race { 
 public static volatile int i = 0;
 public static int uniqueInt() { 
 return i++; 
 }
}

Data Race:

A data race occurs when:
1. 一个进程中的两个或多个线程并发地访问同一内存位置。
2. At least one of the accesses is for writing
3. The threads are not using any exclusive locks to control their accesses to that memory.
Data Race  without a Race Condition:
        Thread 1:
// Thread 1:
void run() { 
 int x = this.getBalance(); // 1
 x += amount; // 2
 this.setBalance(x); // 3
}

        Thread 2:

// Thread 2

int run() { 
 int x = this.getBalance(); // 4
 return x; // 5
}

Critical sections

Code blocks where shared resources are accessed need to be protected (by locks) and are called critical sections.
Execution of critical sections is mutually exclusive, i.e. only one thread can enter a synchronized region of code

访问共享资源的代码块需要受到保护(通过锁),称为临界区。

临界区的执行是互斥的,即只有一个线程可以进入代码的同步区域

下面是生成2,5,10的倍数,并且不混合的例子:

public class MyClass extends Thread {
    private static Object lock; // A static lock object shared by all threads

    static {
        lock = new Object();
    } // A static block to initialize the lock object

    private int value;

    // Customized constructor to initialize private fields
    public MyClass(int value) {
        this.value = value;
    }

    // The main method
    public static void main(String[] args) {
        MyClass thread1 = new MyClass(2); // a thread responsible for multiples of 2
        MyClass thread2 = new MyClass(5); // a thread responsible for multiples of 5
        MyClass thread3 = new MyClass(10); // a thread responsible for multiples of 10
        thread1.start();
        thread2.start();
        thread3.start();
    }

    public void run() {
        synchronized (lock) { // synchronized block for critical region
            for (int i = 1; i <= 1000; i++)
                System.out.println((i * value));
        }
    }
}

线程生命周期

线程池

03-生产者消费者模式(例子见IDEA)

04- The Factory Method Pattern(工厂方法模式)

工厂方法模式介绍:

工厂方法是一种创建式设计模式,它创建了几个派生类的实例。

它定义了一个用于创建对象的接口,但让子类决定要实例化哪个类。工厂方法将对象的创建延迟到子类。

优点

  1. 调用者只需要知道对象的名称即可创建对象。
  2. 扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。
  3. 屏蔽了产品的具体实现,调用者只关心产品的接口。

缺点

每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。

Example:

### **第一步:定义接口 `Item`**
工厂方法模式的核心是抽象产品接口,首先我们需要定义 `Item` 接口:

public interface Item {
    public void onUse(Player player);
}

#### 解释:
- `Item` 是一个接口,代表游戏中的所有道具。
- `onUse(Player player)` 是接口中的方法,定义了道具被玩家使用时的行为。

### **第二步:实现接口 `Item` 的具体产品类**
现在,我们根据游戏中可能出现的具体道具来实现 `Item` 接口。这里有两种道具:`Potion`(药水)和 `Weapon`(武器)。

#### 1. `Potion` 类

public class Potion implements Item {
    @Override
    public void onUse(Player player) {
        System.out.println(player.getName() + " drank a potion!");
        player.recover(50);
    }
}

#### 解释:
- `Potion` 类实现了 `Item` 接口。
- 当玩家使用药水时 (`onUse()` 方法被调用),会打印信息并恢复玩家 50 点生命值。

#### 2. `Weapon` 类

public class Weapon implements Item {
    @Override
    public void onUse(Player player) {
        System.out.println(player.getName() + " has equipped a new weapon!");
        player.equip(this);
    }
}

#### 解释:
- `Weapon` 类也实现了 `Item` 接口。
- 当玩家使用武器时,会打印信息并装备该武器。

### **第三步:创建 `Player` 类**
在实现道具之后,我们还需要有一个 `Player` 类来使用这些道具。

public class Player {
    private String name;
    private int health;
    private Weapon equippedWeapon;

    public Player(String name) {
        this.name = name;
        health = 100;
    }

    public String getName() { return name; }

    public void recover(int hp) {
        health = (health + hp <= 100) ? health + hp : 100;
    }

    public void equip(Weapon weapon) {
        equippedWeapon = weapon;
    }

    public void use(Item item) {
        item.onUse(this);
    }
}

#### 解释:
- `Player` 类表示游戏中的玩家,包含名字、生命值和装备的武器。
- `recover(int hp)` 方法用于恢复生命值,最多为 100。
- `equip(Weapon weapon)` 方法用于装备武器。
- `use(Item item)` 方法调用道具的 `onUse()` 方法,让玩家使用道具。

### **第四步:创建工厂类 `ItemFactory`**
接下来是工厂类的设计,它负责创建具体的 `Item` 实例。

import java.util.Random;

public class ItemFactory {
    public static int count;
    private Random rand;
    static { count = 10; rand = new Random(); }

    public static Item drop() { // 工厂方法
        count--;
        if (count >= 0) {
            int roll = rand.nextInt(10); // 生成 0-9 的随机整数
            if (roll >= 7) {
                return new Weapon(); // 70% 概率生成 Weapon
            } else {
                return new Potion(); // 30% 概率生成 Potion
            }
        } else return null;
    }
}

#### 解释:
- `ItemFactory` 是工厂类,使用 `drop()` 方法随机创建 `Weapon` 或 `Potion` 实例。
- `rand.nextInt(10)` 生成 0 到 9 之间的随机数,如果结果大于等于 7,生成 `Weapon`;否则生成 `Potion`。
- `count` 控制生成道具的总次数,初始值为 10,当 `count` 为 0 时,不再生成任何道具。

### **第五步:创建测试类 `TestClass`**
最后,我们编写测试类来验证工厂方法模式的效果。

public class TestClass {
    public static void main(String[] args) {
        Player player = new Player("Pikachu");
        Item i = ItemFactory.drop();
        if (i != null) player.use(i);
    }
}

#### 解释:
- 创建一个玩家对象 `Pikachu`。
- 使用 `ItemFactory.drop()` 方法获取一个随机生成的道具。
- 如果道具不为空 (`null`),玩家使用该道具。

### **完整的流程总结**
1. **定义接口 (`Item`)**:创建一个抽象接口,定义道具的基本行为。
2. **实现具体产品类 (`Potion` 和 `Weapon`)**:根据具体道具类型,实现接口,定义使用道具时的具体行为。
3. **创建玩家类 (`Player`)**:定义玩家的基本属性和行为,包括使用道具的方法。
4. **创建工厂类 (`ItemFactory`)**:设计工厂类,使用工厂方法 (`drop()`) 来根据随机数生成具体的道具实例。
5. **测试类 (`TestClass`)**:编写测试代码,验证工厂方法模式的效果,展示玩家使用随机生成的道具。

### **工厂方法模式的优点**
- **解耦**:客户端 (`TestClass`) 不直接依赖具体的 `Item` 实现类,而是通过工厂类获取实例,符合面向接口编程。
- **可扩展性**:如果以后需要增加新的道具类型,例如 `Armor`,只需要实现 `Item` 接口,并在 `ItemFactory` 中调整生成逻辑,无需修改客户端代码。
- **易维护**:通过使用工厂方法,具体产品的创建逻辑集中在工厂类中,便于维护和修改。

工厂方法模式的类图:

05- The Abstract Factory Pattern(抽象工厂模式)

  • 抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
  • 此模式允许类将实例化推迟到子类。
  • 这意味着抽象工厂允许一个类返回一个类的工厂。因此,抽象的工厂模式比工厂模式高出一个层次。这家工厂也被称为工厂中的工厂。
  • 常用用法:
    • 1.当系统需要独立于其对象的创建、组合和表示方式时。
    • 2.当相关对象族必须一起使用时,则需要强制执行此约束。
    • 3.当您希望提供一个不显示实现而只显示接口的对象库时。
    • 4.当系统需要配置多个对象族中的一个时。 

Example:

Step 1: Create an Interface
public interface Shape {
    public void draw();
}
Step 2: Create the Concrete Classes
public class RoundedRectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside RoundedRectangle::draw() method.");
    }
}

public class RoundedSquare implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside RoundedSquare::draw() method.");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}
Step 3: Create an Abstract Class, i.e. General Factory
public abstract class AbstractFactory {
    public abstract Shape getShape(String shapeType);
}
Step 4: Create the Concrete Factories
public class ShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("RECTANGLE")) return new Rectangle();
        else if (shapeType.equalsIgnoreCase("SQUARE")) return new Square();
        return null;
    }
}

public class RoundedShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("RECTANGLE")) return new RoundedRectangle();
        else if (shapeType.equalsIgnoreCase("SQUARE")) return new RoundedSquare();
        return null;
    }
}
Step 5: Create a Factory to Generate Objects
public class FactoryProducer {
    public static AbstractFactory getFactory(boolean rounded) {
        if (rounded) return new RoundedShapeFactory();
        return new ShapeFactory();
    }
}

Step 6: Write the Application Class

public class AbstractFactoryPatternDemo {
 public static void main(String[] args) {
 // get shape factory
 AbstractFactory shapeFactory = FactoryProducer.getFactory(false);
 // get an object of Shape Rectangle, and draw the Shape Rectangle
 Shape shape1 = shapeFactory.getShape("RECTANGLE"); shape1.draw();
 // get an object of Shape Square, and draw the Shape Square
 Shape shape2 = shapeFactory.getShape("SQUARE"); shape2.draw();
 // get shape factory
 AbstractFactory shapeFactory1 = FactoryProducer.getFactory(true);
 // get an object of Shape Rectangle, and draw the Shape Rectangle
 Shape shape3 = shapeFactory1.getShape("RECTANGLE"); shape3.draw();
 // get an object of Shape Square, and draw the Shape Square
 Shape shape4 = shapeFactory1.getShape("SQUARE"); shape4.draw();
 }
}


/*
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside RoundedRectangle::draw() method.
Inside RoundedSquare::draw() method.
*/

Structural Design Patterns

01-组合模式(Composite Pattern): 文件夹和文件

是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

  • 叶子节点(Leaf Class):

    • 表示组合中的叶子节点对象,叶子节点没有子节点。
    • 它实现了组件接口的方法,但通常不包含子组件。
  • 复合节点(Composite Class):

    • 表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。
    • 它实现了组件接口的方法,包括管理子组件的方法。
  • 组件(Component Interface):

    • 定义要在叶对象和复合对象上执行的方法 e.g. move(), scale()

Example:

Step 1: Create Employee Class having List of Employee Objects
创建  Employee 类,该类带有  Employee 对象的列表。
import java.util.ArrayList;
import java.util.List;
 
public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates;
 
   //构造函数
   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }
 
   public void add(Employee e) {
      subordinates.add(e);
   }
 
   public void remove(Employee e) {
      subordinates.remove(e);
   }
 
   public List<Employee> getSubordinates(){
     return subordinates;
   }
 
   public String toString(){
      return ("Employee :[ Name : "+ name 
      +", dept : "+ dept + ", salary :"
      + salary+" ]");
   }   
}
Step 2: Use the Employee Class to Create
使用  Employee 类来创建和打印员工的层次结构。
public class CompositePatternDemo {
   public static void main(String[] args) {
      Employee CEO = new Employee("John","CEO", 30000);
 
      Employee headSales = new Employee("Robert","Head Sales", 20000);
 
      Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
 
      Employee clerk1 = new Employee("Laura","Marketing", 10000);
      Employee clerk2 = new Employee("Bob","Marketing", 10000);
 
      Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
      Employee salesExecutive2 = new Employee("Rob","Sales", 10000);
 
      CEO.add(headSales);
      CEO.add(headMarketing);
 
      headSales.add(salesExecutive1);
      headSales.add(salesExecutive2);
 
      headMarketing.add(clerk1);
      headMarketing.add(clerk2);
 
      //打印该组织的所有员工
      System.out.println(CEO); 
      for (Employee headEmployee : CEO.getSubordinates()) {
         System.out.println(headEmployee);
         for (Employee employee : headEmployee.getSubordinates()) {
            System.out.println(employee);
         }
      }        
   }
}
Output from the Composite Pattern Example
Employee :[ Name : John, dept : CEO, salary :30000 ]
Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
Employee :[ Name : Richard, dept : Sales, salary :10000 ]
Employee :[ Name : Rob, dept : Sales, salary :10000 ]
Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
Employee :[ Name : Bob, dept : Marketing, salary :10000 ]

02- Facade(外观模式: 基金

Create a list of functions that can be used directly without deep knowledge about how the classes interact with each other.

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

优点

  1. 减少依赖:客户端与子系统之间的依赖减少。
  2. 提高灵活性:子系统的内部变化不会影响客户端。
  3. 增强安全性:隐藏了子系统的内部实现,只暴露必要的操作。

缺点

  • 违反开闭原则:对子系统的修改可能需要对外观类进行相应的修改

Example:

Step 1: Create an Interface
public interface Shape {
   void draw();
}
Step 2: Create the Concrete Classes
public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}
public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}
public class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}
Step 3: Create a Facade Class
public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;
 
   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }
 
   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}
Step 4: Write the Application Class using the Facade
public class FacadePatternDemo {
   public static void main(String[] args) {
      ShapeMaker shapeMaker = new ShapeMaker();
 
      shapeMaker.drawCircle();
      shapeMaker.drawRectangle();
      shapeMaker.drawSquare();      
   }
}

03- Adapter(适配器模式):转换插头

充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个类的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

Match interfaces of different classes.Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

Example:

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。

我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。

AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式

Step 1: Create Interfaces
public interface MediaPlayer {
   public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer { 
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}
Step 2: Create Concrete Classes
public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);      
   }
 
   @Override
   public void playMp4(String fileName) {
      //什么也不做
   }
}
public class Mp4Player implements AdvancedMediaPlayer{
 
   @Override
   public void playVlc(String fileName) {
      //什么也不做
   }
 
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);      
   }
}
Step 3: Create an Adapter Class
public class MediaAdapter implements MediaPlayer {
 
   AdvancedMediaPlayer advancedMusicPlayer;
 
   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();       
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }  
   }
 
   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}
Step 3b: Create an Adaptor Class of an Adaptor
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 
 
   @Override
   public void play(String audioType, String fileName) {    
 
      //播放 mp3 音乐文件的内置支持
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);         
      } 
      //mediaAdapter 提供了播放其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+
            audioType + " format not supported");
      }
   }   
}
Step 4: Write the Application Class using the Adaptor
public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
 
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

//结果:
/*
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

*/

04- Wapper(包装模式):

Façade, Adaptor and Decorator are often informally called "wrappers
Wrappers bridges worlds! Sometimes your code and your colleague's code can be worlds
apart, so it's important to know wrappers
Wrapper Pattern is an informal but general concept covering Facade,
Adaptor and Decorator patterns.
Bridge : Separates an object’s interface from its implementation. Decouple an
abstraction from its implementation so that the two can vary independently.
Decorator : Add responsibilities to objects dynamically. Attach additional
responsibilities to an object dynamically. Decorators provide a flexible
alternative to subclassing for extending functionality.
Flyweight : Used to reduce the number of objects created and to decrease
memory footprint and increase performance, and provides ways to decrease
object count thus improving the object structure of application.
Proxy : Create object having original object to interface its functionality to
outer world

Behavioural Design Patterns

  1. Observer: A way of notifying change to a number of classes. Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
  2. Iterator: Sequentially access the elements of a collection. Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
  3. Strategy: Encapsulates an algorithm inside a class. Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
  4. Template Method: Defer the exact steps of an algorithm to a subclass. Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
  5. Chain of Responsibility: A way of passing a request between a chain of objects. Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
  6. Command: Encapsulate a command request as an object. Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
  7. Interpreter: A way to include language elements in a program. Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language
  8. Mediator: Defines simplified communication between classes. Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently
  9. Memento: Capture and restore an object's internal state. Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
  10. State: Alter an object's behavior when its state changes. Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
  11. Visitor: Defines a new operation to a class without change. Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

01- The Observer Pattern(观察者模式)

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

结构:

  • 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

Example:

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

Step 1: Create Subject Class
import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}
Step 2: Create Observer Abstract Class
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}
Step 3: Create the Observer Concrete Classes
public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " 
      + Integer.toBinaryString( subject.getState() ) ); 
// Integer.toBinaryString(subject.getState()) 用于将一个整数转换为二进制字符串表示的静态方法
   }
}
public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " 
     + Integer.toOctalString( subject.getState() ) ); 
   }
}
public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " 
      + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

Step 4: Write the Application Class
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}

/*
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010
*/

02- The Iterator Pattern(迭代器模式):

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

迭代器模式属于行为型模式。

结构

迭代器模式包含以下几个主要角色:

  • 迭代器接口(Iterator):定义了访问和遍历聚合对象中各个元素的方法,通常包括获取下一个元素、判断是否还有元素、获取当前位置等方法。

  • 具体迭代器(Concrete Iterator):实现了迭代器接口,负责对聚合对象进行遍历和访问,同时记录遍历的当前位置。

  • 聚合对象接口(Aggregate):定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。

  • 具体聚合对象(Concrete Aggregate):实现了聚合对象接口,负责创建具体的迭代器对象,并提供需要遍历的数据。

Java Collections!

Collection (container) provides a set of interfaces and classes to implement various data structures to hold the data, and algorithms for data manipulation.
集合(容器)提供了一组接口和类来实现保存数据的各种数据结构,以及用于数据操作的算法
  • Data structures include:
    • 1. Array, Vector
    • 2. ArrayList, LinkedList
    • 3. Set, HashSet, TreeSet
  • Algorithms include:
    • 1. Search
    • 2. Sort
    • 3. Insertion and Deletion
    • 4. Reverse
    • 5. Shuffle
Java Collections:
  • 1. Sequence Collections
    • Array, i.e. [ ]
    • List: ArrayList, LinkedList
    • Vector
  • 2.  Associative Collections: Data is accessed using the key instead of indexes
    • Set: HashSet , TreeSet
    • Map: HashMap , TreeMap
  • Almost Collections
    • Collection
Based on ordering property of keys.
Keys need to be comparable using < (less than) operator.
Hierarchy of Collection Framework:

Example: Vector: Insertion

import java.util.Vector;
import java.util.ArrayList;

public class VectorDemo1_Insert{
    static Vector<Integer> addCollectionMethod(Vector vec) {
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(20);
        a.add(40);
        a.add(50);
        vec.addAll(a); // 将 ArrayList 中的所有元素添加到 Vector
        return vec;
    }

    public static void main(String[] args) {
        Vector<Integer> v = new Vector<Integer>();
        v.add(10); // 添加元素 10 到 Vector
        v = addCollectionMethod(v); // 调用方法,将 ArrayList 的元素添加到 Vector
        System.out.println(v);
        v.addElement(60); // 使用 Vector 的遗留方法添加元素
        v.insertElementAt(30, 2); // 在索引 2 处插入元素 30
        System.out.println(v);
    }
}

Example: Vector: Deletion:

import java.util.*;

public class VectorDemo2_Deletion{
    static Vector<Integer> VectorDeletion(Vector vec){
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(10); a.add(20); a.add(30); a.add(40); a.add(50); a.add(60); a.add(70);
        vec.addAll(a);
        return vec;
    }

    public static void main(String[] args){
        Vector<Integer> v = new Vector<Integer>();
        v = VectorDeletion(v);
        v.remove(1);          //移除index为1的元素
        v.removeElement(40);  //移除为40的元素
        v.removeElementAt(4); //移除index为4的元素
        System.out.println(v);
        v.removeAllElements();
        System.out.println(v);
    }
}

Example: HashSet: Search by contains()

import java.io.*;
import java.util.*;
public class HashSetDemo {
    public static void main(String[] args) {
        // Creating an empty Set
        Set<String> set = new HashSet<String>();
        set.add("love"); set.add("you"); set.add("java");
        System.out.println("Set:" + set);
        // Check if the Set contains "love"
        System.out.println("Does love in set? "+ set.contains("love"));
        System.out.println("Does java in set? "+ set.contains("java"));
        // Check if the Set contains "Denny"
        System.out.println("Does Denny in set? "+ set.contains("Denny"));
    }
}

Example: LinkedList: Reverse

import java.util.*;
public class LinkListDemo {
    public static void main(String[] args) {
        // Declaring linkedlist without any initial size
        LinkedList<Integer> list = new LinkedList<Integer>();
        // Appending elements at the end of the list
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println("Elements before reversing: " +list);
        // Collections.reverse method takes a list as a parameter
        Collections.reverse(list);
        System.out.println("Elements after reversing: " +list);
    }
}

Example: ArrayList: Shuffle

import java.util.*;
public class ArrayListDemo_Shuffle {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("I");
        list.add("love");
        list.add("java");
        list.add("and");
        list.add("Denny");

        System.out.println("Original List :"+list); // Original List :[I, love, java, and, Denny]
        Collections.shuffle(list);
        System.out.println("Shuffle List :"+list); // Shuffle List :[love, java, and, I, Denny]
    }
}

Example:ArryaList: Sort by reverse order

import java.util.*;
public class ArrayListDemo_reverse {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("I");
        list.add("love");
        list.add("java");
        list.add("and");
        list.add("Denny");

        System.out.println("Original List :"+list); // Original List :[I, love, java, and, Denny]
        Collections.sort(list,Collections.reverseOrder());
        System.out.println("After sorting: " +list); // After sorting: [love, java, and, I, Denny]
    }
}

Generic Data Type:

泛型类的格式:
修饰符 class 类名<类型变量,类型变量,….>{}注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等


泛型中的类型必须是引用数据类型!!!

Interface Enumeration<E>

  • 遍历功能

    • Enumeration 提供了一种简单的方法来遍历集合中的元素。
    • 它没有像 Iterator 那样的删除操作,只能读取元素。
  • Concrete Class for Enumeration
import java.util.Enumeration;
import java.lang.reflect.Array;
public class Matrix<E> implements Enumeration<E> { // a concrete class for Enumeration
 private int row, column, currentRow, currentColumn;
 private E[][] data;
 public Matrix(Object obj) {
 Class type = obj.getClass();
 if (!type.isArray()) throw new IllegalArgumentException("Invalid type: " + type);
 row = Array.getLength(obj); column = Array.getLength(obj[0]);
 data = new E[row][column];
 for (int i = 0; i < row; i++) for (int j = 0; j < column; j++)
 data[i][j] = new E(obj[i][j]);
 }
 @Override public boolean hasMoreElements() { 
 return currentRow!=row-1 || currentColumn!=column-1; 
 }
 @Override public E nextElement() {
 if (++currentColumn == column) { currentColumn = 0; currentRow++; }
 return data[currentRow][currentColumn];
 }
}

  • Write the Application Class
public class MatrixDemo { // an application class
 public static void main(String[] args) { 
 String[][] strArray = {{"Apple","Orange"},{"Banana","Grape"}};
 Integer[][] intArray = new Integer[2][3]; // {{0,0,0},{0,0,0}}
 // {{5,7,0},{3,1,0}}
 intArray[0][0] = 5; intArray[0][1] = 7; intArray[1][0] = 3; intArray[1][1] = 1;
 Matrix<String> m1 = new Matrix(strArray);
 while (m1.hasMoreElements()) { System.out.print(m1.nextElement() + " "); }
 System.out.println();
 Matrix<Integer> m2 = new Matrix(intArray);
 while (m2.hasMoreElements()) { System.out.print(m1.nextElement() + " "); }
 }
}

Example:

我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。

IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names

步骤 1

创建接口:

public interface Iterator {
   public boolean hasNext();
   public Object next();
}
public interface Container {
   public Iterator getIterator();
}

步骤 2

创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator

public class NameRepository implements Container {
   public String[] names = {"Robert" , "John" ,"Julie" , "Lora"};
 
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
 
   private class NameIterator implements Iterator {
 
      int index;
 
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
 
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}

步骤 3

使用 NameRepository 来获取迭代器,并打印名字

public class IteratorPatternDemo {
   
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
 
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}

/*
Name : Robert
Name : John
Name : Julie
Name : Lora

*/

03- Strategy Pattern(策略模式)(商场促销活动)

在策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

结构

策略模式包含以下几个核心角色:

  • 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
  • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
  • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。

实现

我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

步骤 1

创建一个接口。

public interface Strategy {
   public int doOperation(int num1, int num2);
}

步骤 2

创建实现接口的实体类。

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

步骤 3

创建 Context 类。

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

步骤 4

使用 Context 来查看当它改变策略 Strategy 时的行为变化。

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

04- The Template Method Pattern

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

关键代码

  • 模板方法:在抽象父类中定义,调用抽象方法和具体方法。
  • 抽象方法:由子类实现,代表算法的可变部分。
  • 具体方法:在抽象父类中实现,代表算法的不变部分。

包含的几个主要角色

  • 抽象父类(Abstract Class)

    • 定义了模板方法和一些抽象方法或具体方法。
  • 具体子类(Concrete Classes)

    • 继承自抽象父类,并实现抽象方法。
  • 钩子方法(Hook Method)(可选)

    • 在抽象父类中定义,可以被子类重写,以影响模板方法的行为。
  • 客户端(Client)(可选)

    • 使用抽象父类和具体子类,无需关心模板方法的细节。

实现

我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。

TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

模板模式的 UML 图

Step1: 创建一个抽象类,它的模板方法被设置为 final。

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   //模板
   public final void play(){
 
      //初始化游戏
      initialize();
 
      //开始游戏
      startPlay();
 
      //结束游戏
      endPlay();
   }
}

Step2:创建扩展了上述类的实体类

public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}

Step3: 使用 Game 的模板方法 play() 来演示游戏的定义方式。 

public class TemplatePatternDemo {
   public static void main(String[] args) {
 
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();      
   }
}
  • 模板方法与策略模式密切相关。在策略模式中,我们将一个算法的全部或部分或行为外部化到另一个对象上,这样我们就可以随意切换它们,或者在以后轻松地引入新的行为。
  • 在模板方法中,我们期望子类完成与策略对象类似的角色,但在模板中,我们期望提供一组已经定义的特定策略。
  • 理解模板方法的最好方法是,它类似于填写空白,就像右边的快乐父亲节的信。

Object Copy and Operator Overloading

Different Levels of Copying (Cloning)

  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。

  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

1.  Object assignment : Assign two reference variables to look at the same object
2. Shadow copy : Reflect changes made to the new object in the original object
3. Deep copy : Store copies of the object's value

Default Behaviour of Clonable is Shadow Copy (v1) 

import java.lang.Cloneable;
public class MyClass implements Cloneable {
 public int num;
 public Integer[] intArray;
 public MyClass(int num, Integer[] intArray) {
 this.num = num; this.intArray = new Integer[intArray.length];
 for (int i = 0; i < intArray.length; i++) this.intArray[i] = intArray[i];
 }
 @Override protected Object clone() throws CloneNotSupportedException {
 return super.clone(); // Shadow copy: only copy primitive data
 }
 public static void main(String args[]) {
 Integer[] arr = new Integer[4]; arr[0] = 5; arr[1] = 7; arr[2] = 3; arr[3] = 1;
 MyClass obj1 = new MyClass(10, arr);
 MyClass obj2 = null;
 try { obj2 = (MyClass)obj1.clone(); } catch (CloneNotSupportedException e) {
 System.out.println("Error: " + e.getMessage());
 } 
 System.out.println("Before: "+obj1.intArray[0]+" "+obj2.intArray[0]);
 obj1.intArray[0] = 6;
 System.out.println("After : "+obj1.intArray[0]+" "+obj2.intArray[0]);
 }
}

Modify the clone() method to Deep Copy (v2) 

@Override protected Object clone() throws CloneNotSupportedException {
 // Assign the shallow copy to new reference variable t
 MyClass t = (MyClass)super.clone();
 
 // Create a new object for the field intArray, and assign it to shallow copy 
 // obtained, to make it a deep copy
 t.intArray = new Integer[intArray.length];
 for (int i = 0; i < intArray.length; i++) t.intArray[i] = intArray[i];
 return t;
 }

 Copy Constructor: Employee Class

A copy constructor in a Java class is a constructor that creates an object using another object of the same Java class .
public class Employee {
 private int id;
 private String name;
 public Employee(){} // default constructor
 public Employee(int id, String name) { // customized constructor
 this.id = id;
 this.name = name;
 }
 public Employee(Employee employee) { // copy constructor
 this.id = employee.id;
 this.name = employee.name;
 }
}

Immutable Objects

An immutable object (unchangeable object) is an object whose state cannot
be modified after it is created.
If this is a read-only object, we can use either final specifier or skipping the
setter implementation of a private field.
We can use either clone method or copy constructor to pass the copy(clone)
of the object into other methods.

 

Operator Overloading Unsupported in Java

The only operator overloading in Java is string concatenation.
All remaining operator overloading is not supported.
// addition for complex, no operator overloading, but we can override toString() method
public class Complex {
 private double real, imaginary;
 public Complex(double real, double imaginary) {
 this.real = real; this.imaginary = imaginary;
 }
 public Complex add(Complex other) {
 return new Complex(real+other.real, imaginary+other.imaginary);
 }
 @Override public String toString() {
 return real + "+" + imaginary + "i";
 }
 public static void main(String[] args){
 Complex c1 = new Complex(1,2), c2 = new Complex(2,3);
 System.out.println(c1 + " + " + c2 + " = " + c1.add(c2));
 }
}

UML

1. 类图(Class Diagram)

定义:

类与类之间的表示方式:

类图示例:

练习:

按如下描述绘制出“飞船系统”的类图。
1. 神舟六号飞船是神舟飞船系列的一种,它由轨道舱、返回舱和逃逸救生塔组成。
2. 航天员可以在返回舱内驾驶飞船,轨道舱则是航天员工作和休息的场所。在紧急情况下,可以利用逃逸救生塔逃生。
3. 在飞船两侧有多个太阳能电池翼,可以为飞船提供电能。
————————————————

2. Use Case Diagrams 用例图

什么是用例图?

用例图是UML的一种结构化图表,用于描述系统的功能及其与外部角色的交互。它回答以下三个关键问题:

  1. 描述什么? 系统的功能范围(用例)。
  2. 谁与系统交互? 外部实体或角色(演员)。
  3. 这些实体能做什么? 系统提供的功能(用例)。

用例图的组成要素

  1. 角色(Actors)

    • 外部实体或用户,系统的使用者。
    • 用一个人形符号或带有 <<actor>> 标记的名称表示。
    • 角色永远在系统边界之外,不属于系统的一部分。
  2. 用例(Use Cases)

    • 系统提供的功能,表示为椭圆形。
    • 每个用例表示系统中一个具体的功能。
  3. 关联(Associations)

    • 描述角色与用例之间的交互关系,用直线连接。
    • 关联可以标注多重性,例如 1..3 表示一个角色可执行用例 1 到 3 次。
  4. 关系

    • 泛化(Generalization):角色或用例之间的继承关系。
    • 包含(Include):一个用例必须包含另一个用例的行为。
    • 扩展(Extend):一个用例可以在特定条件下扩展另一个用例的行为。

用例图示例

3. State Machine Diagram

什么是状态机图?

状态机图是UML的一种行为图,用于描述对象的生命周期,展示对象可能的状态及其因事件发生而进行的状态转换。它主要回答以下问题:

  1. 可能的状态是什么? 系统或对象的状态集合。
  2. 状态如何转换? 由于事件或条件触发的状态变化。
  3. 在每个状态中系统/对象会做什么? 每个状态下的行为。

状态机图的组成部分

  1. 状态(States)

    • 系统或对象的某种状态,用圆角矩形表示。
    • 一个状态可以包含内部活动:
      • Entry:进入状态时执行的活动。
      • Do:状态持续时执行的活动。
      • Exit:退出状态时执行的活动。

  1. 状态转换(State Transitions)

    • 表示状态之间的转移,用箭头表示。
    • 转移由事件或条件触发,箭头上标注事件名称或条件。
    • 示例:lecture start → occupied

  1. 初始状态和结束状态

    • 初始状态:表示状态机的起点,用实心圆表示。
    • 结束状态:表示状态机的终点,用带圆圈的实心圆表示。

UML状态机的两种类型

  1. 行为状态机(Behavior State Machine)
    • 常见且实用,描述对象的行为随状态变化而变化。
  2. 协议状态机(Protocol State Machine)
    • 描述协议的合法状态序列,较少用于普通对象建模。

状态机图示例:

 4. Sequence Diagrams

什么是序列图?

序列图是UML的一种行为图,用于建模对象之间的交互行为,描述系统中对象如何通过消息传递协同工作。它特别适合描述系统中的时间顺序和动态交互。


序列图的组成部分

  1. 生命线(Lifeline)

    • 表示交互的参与者(可以是人类用户或非人类组件,例如服务器或数据库)。
    • 矩形框+虚线表示,矩形框中标注参与者的角色或类名称。
    • 生命线表示对象的生命周期,通常贯穿整个序列图。
    • 示例:

      Student --------------- :Professor ---------------

  2. 消息(Messages)

    • 表示对象之间的交互,按时间顺序传递消息。
    • 消息的类型
      • 同步消息:发送者发送消息后,等待接收者返回响应再继续操作。用实线箭头表示。
      • 异步消息:发送者发送消息后,不等待接收者响应即可继续操作。用虚线箭头表示。
      • 响应消息:接收者处理完成后返回的消息。用虚线箭头表示。
    • 示例:

      register(course) → 确认注册 confirm(course, "ok")

  3. 激活(Activation)

    • 表示对象在某个时刻执行某个操作,用生命线上的矩形框表示。
  4. 创建和销毁

    • 对象创建:用带开口箭头的虚线表示,箭头指向新对象的生命线顶部。
    • 对象销毁:用生命线末尾的“X”标记。

序列图的结构

  • 序列图从上到下按照时间顺序展示交互。
  • 交互的对象用垂直虚线表示,其上标注消息传递顺序。
  • 时间顺序从顶部向下推进,越靠下发生的时间越晚。

序列图示例:

练习:


用顺序图表达出顾客插入卡到提款机以及输入密码的过程:
1. 插入卡,输入密码;
2. 密码正确,进入下一步菜单;
3. 密码不正确,提示再次输入密码;
4. 三次输入不正确,吞卡。
————————————————

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值