设计模式GOF

news2024/11/17 9:43:50

        设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 

  • 设计模式的意义:
    • 提高程序员思维能力、编程能力和设计能力。
    • 是程序更加标准化、代码编制工程化,提高效率,缩短开发周期。
    • 使设计的代码可重用性高、可读性强、可靠性高、可维护性强。
  • 创建型模式
    • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
  • 结构型模式
    • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为模式
    • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

OOP七大原则

  • 开闭原则:对扩展开放,对修改关闭。
  • 里氏替换原则:继承必须确保超类所拥有的的性质在子类中仍然成立。
  • 依赖倒置原则::要面向接口编程,不要面向实现编程。
  • 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
  • 接口隔离原则:为各个类建立他们需要而专用接口。
  • 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。
  • 合成复用原则:尽量使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

创建型 --- 单例模式

单例模式指在某个系统中一个类只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。

单例模式思想 构造器私有

饿汉式单例模式

饿汉模式,即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。

// 饿汉式单例模式
public class Hungry {

    // 饿汉式单例模式的缺点 ----> 可能会浪费空间      最好是 当需要使用时在初始化对象
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    private Hungry(){       // 构造器私有化 单例模式重要思想

    }

    private final static Hungry HUNGRY = new Hungry();   //饿汉式 先准备好 一上来就把对象准备好了

    public static Hungry getInstance(){   // 开放一个对外的方法 获取HUNGRY
        return HUNGRY;
    }

}

懒汉式单例模式  要用的时候再去加载

饿汉模式存在没有必要的消耗,浪费空间。基于这种考虑,于是就有了懒汉模式,也就是在需要的时候再对单例进行实例化

public class LazyMan{
    private LazyMan(){
        
    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

单线程下单例是可以的 多线程并发场景下会有问题

public class LazyMan{
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }


    public static void main(String[] args) {
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

双重检测锁模式的懒汉式单例 简称DCL懒汉式

public class LazyMan_DCL{
    private LazyMan_DCL(){
    }

    private static LazyMan_DCL lazyMan;

    public static LazyMan_DCL getInstance(){
        if(lazyMan == null){
            synchronized (LazyMan_DCL.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan_DCL(); 
                }
            }
        }
        return lazyMan;  
    }
}

 但是存在指令重排的问题

public static LazyMan_DCL getInstance(){
        if(lazyMan == null){
            synchronized (LazyMan_DCL.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan_DCL();  //不是原子性操作 在极端情况下还是会出现问题的
                    /**
                     *  1. 分配内存空间
                     *  2、执行构造方法,初始化对象
                     *  3、把这个对象指向这个空间
                     *
                     *  会发生指令重排现象
                     *       123
                     *  变成  132
                     */
                }
            }
        }
        return lazyMan;   //有可能因为指令重排导致返回null对象

加上volatile关键字防止指令重排

public class LazyMan_DCL{
    private LazyMan_DCL(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private volatile static LazyMan_DCL lazyMan;  //所以因为下面的涉及指令重排的问题 需要加上volatile关键字

    public static LazyMan_DCL getInstance(){
        if(lazyMan == null){
            synchronized (LazyMan_DCL.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan_DCL();  //不是原子性操作 在极端情况下还是会出现问题的
                    /**
                     *  1. 分配内存空间
                     *  2、执行构造方法,初始化对象
                     *  3、把这个对象指向这个空间
                     *
                     *  会发生指令重排现象
                     *       123
                     *  变成  132
                     */
                }
            }
        }
        return lazyMan;   //有可能因为指令重排导致返回null对象
    }
}

相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。

核心作用
        保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

常见场景
        项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取网站的计数器一般也会采用单例模式,可以保证同步
        数据库连接池的设计一般也是单例模式
        在Servlet编程中,每个Servlet也是单例的
        在Spring中,每个Bean默认就是单例的

创建型 --- 工厂模式

实现创建者和调用者的分离

核心本质:

  • 实例化对象不适用new, 用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制,从而将调用者跟我们的实现类解耦

三种模式:

  • 简单工厂模式
    • 用来生产同一等级结构中的任何产品(对于增加新的产品,需要扩展已有代码)
  • 工厂方法模式
    • 用来生产同一等级结构中的固定产品(支持增加任意产品)
  • 抽象工厂模式
    • 围绕一个超级工厂创建其他工厂,该超级工厂又被称为其他工厂的工厂

简单工厂模式

定义一个接口 

public interface Car
{
    void name();
}

接口的实现类

public class WuLing implements Car
{
    @Override
    public void name()
    {
        System.out.println("五菱宏光!");
    }
}
public class Model implements Car
{
    @Override
    public void name()
    {
        System.out.println("特斯拉!");
    }
}

简单工厂

// 静态工厂模式
public class CarFactory
{
    // 方法一  违反了开闭原则
    public static Car getCar(String car)
    {
        if ("五菱".equals(car))
        {
            return new WuLing();
        }
        else if ("特斯拉".equals(car))
        {
            return new Model();
        }
        else
        {
            return null;
        }
    }
    // 方法二
    public static Car getWuLing()
    {
        return new WuLing();
    }
    public static Car getModel()
    {
        return new Model();
    }
}

输出 

public class Consumer
{
    public static void main(String[] args)
    {
        // 正常方法
        WuLing wuLing = new WuLing();
        Model model = new Model();
        wuLing.name();
        model.name();
        // 使用工厂创建 方法一
        Car wuLingF = CarFactory.getCar("五菱");
        wuLingF.name();
        Car modelF = CarFactory.getCar("特斯拉");
        modelF.name();
        // 方法二
        Car wuLing2 = CarFactory.getWuLing();
        Car model2 = CarFactory.getModel();
        wuLing2.name();
        model2.name();
    }
}

违反了开闭原则

工厂方法模式

创建工厂方法

// 工厂方法模式
public interface CarFactory
{
    Car getCar();
}
public class WuLingFactory implements CarFactory
{
    @Override
    public Car getCar()
    {
        return new WuLing();
    }
}
public class ModelFactory implements CarFactory
{
    @Override
    public Car getCar()
    {
        return new Model();
    }
}

输出

public class Consumer
{
    public static void main(String[] args)
    {
        Car car_WuLing = new WuLingFactory().getCar();
        Car car_Model = new ModelFactory().getCar();
        car_WuLing.name();
        car_Model.name();
        Car car_xiaoPeng = new XiaoPengFactory().getCar();
        car_xiaoPeng.name();
    }
}

结构型 --- 代理模式

Spring AOP底层 应用了动态代理

现实的案例

 

静态代理

抽象角色 租房接口

//租房
public interface Rent {
    public void rent();
}

真实角色 房东 

//房东
public class Host implements Rent {
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}

代理角色 中介 

public class Proxy implements Rent {

    private Host host;

    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }

    public void rent() {
        seeHouse();
        host.rent();
        hetong();
        fare();
    }

    //看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }

    //看房
    public void hetong(){
        System.out.println("签租赁合同");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }

}

客户端访问代理角色   租客 

public class Client {
    public static void main(String[] args) {
        //房东要租房子
        Host host = new Host();
        //代理,中介帮房东租房子,但是呢?代理角一般会有一些附属操作!
        Proxy proxy = new Proxy(host);
        //你不用面对房东,直接找中介租房即可!
        proxy.rent();
    }
}

代理模式的好处

        1. 可以使真实角色的操作更加纯粹,不用去关心一些公共业务

        2. 公共业务就交给代理角色, 实现业务的分工

        2. 公共业务发送扩展的时候,方便集中管理

缺点:

        1. 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率变低

        

另一个静态代理的实例 

接口

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

真实对象

//真实对象
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void query() {
        System.out.println("查询了一个用户");
    }

    //1.改动原有的业务代码,在公司中是大忌!
}

代理对象

public class UserServiceProxy implements UserService {

    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.add();
    }

    public void update() {
        log("update");
        userService.add();
    }

    public void query() {
        log("query");
        userService.add();
    }

    //日志方法
    public void log(String msg){
        System.out.println("[Debug] 使用了"+msg+"方法");
    }

}

访问代理对象

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);

        proxy.query();

    }
}

动态代理

1. 动态代理和静态代理的角色一致

2. 动态代理使用反射动态生成代理类,来修正静态代理的缺点。 静态代理的代理类需要手动实现

3. 动态代理分为两大类:基于接口的动态代理 和 基于类的动态代理

        基于接口的动态代理----- JDK动态代理

        基于类的动态代理 ---- cglib

        java字节码实现

JDK动态代理

需要了解两个类 Proxy: 代理     InvocationHandler: 调用处理程序

Proxy类: 代理

Proxy提供了创建动态代理类和实例的静态方法

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                                      InvocationHandler h)

InvocationHandler: 调用处理程序

只有一个invoke方法

 

抽象角色 租房接口

//租房
public interface Rent {
    public void rent();
}

真实角色 房东

//房东
public class Host implements Rent {
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}

动态生成代理对象

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    /**
     * 动态代理有两种:
     *      1.jdk动态代理 要求必须有接口,最终生成的代理类和目标类实现相同的接口
     *          最终生成的代理类在com.sun.proxy包下,类名为$proxy2
     *      2.cglib动态代理 最终生成的代理类会继承目标类,并且和目标类在相同的包下
     */

    /**
     *  Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
     *  这个方法一共有 3 个参数:
     *      loader :类加载器,用于加载代理对象。  指定加载动态生成的代理类的类加载器
     *      interfaces : 被代理类实现的一些接口;  代理类和目标类实现相同的接口 获取目标对象实现的所有接口的class对象的数组
     *      h : 实现了 InvocationHandler 接口的对象;  执行处理  代理类是动态生成的 代理类如何去重写抽象方法 设置代理类的抽象方法如何重写
     */
    //生成任意目标所需要的得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }


    /**
     * 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
     *
     *      * 当你使用代理对象调用方法的时候实际会调用到这个方法 method.invoke(rent, args);
     */
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // proxy代表代理对象 method表示要执行的方法 args要执行的方法的参数列表
        //动态代理的本质,就是使用反射机制实现!
        seeHouse();   //功能实现之前
        Object result = method.invoke(rent, args); //目标对象实现功能的过程
        fare();  //功能实现之后
        return result;
    }

    public void seeHouse(){
        System.out.println("中介带看房子");
    }

    public void fare(){
        System.out.println("收中介费");
    }
}

访问代理对象

public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();

        //代理角色 : 现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象!
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy(); //这里的proxy就是动态生成的,我们并没有写~
        proxy.rent();
    }
}

结构型 --- 适配器模式

以现实生活中的例子为例

 将一个类的接口转换成客户希望的另外一个接口Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!

1. 类适配器模式(通过继承, 单继承)

网线

// 要被适配的类: 网线
public class WebLine {
    public void connect(){
        System.out.println("连接网线上网");
    }
}

电脑

// 客户端类  想上网 但是插不上网线
public class Computer {
    // 我们电脑需要连接上转接器才能上网
    public void net(NetToUsb adapter){
        // 上网的具体实现 找一个转接头
        adapter.handleRequest();
    }
    public static void main(String[] args) {
        // 电脑 适配器 网线
        WebLine adaptee = new WebLine();
        Adapter adapter = new Adapter();
        Computer computer = new Computer();

        computer.net(adapter);
    }
}

 接口

// 接口转换器的抽象实现
public interface NetToUsb {

    //作用 处理请求  网线 --> usb
    public void handleRequest();
}

适配器

// 真正的适配器  需要连接usb 连接网线
public class Adapter extends WebLine implements NetToUsb {
    @Override
    public void handleRequest() {
        super.connect(); // 可以上网了
    }
}

2. 对象适配器 组合的方式(常用)

// 真正的适配器  需要连接usb 连接网线
public class Adapter2 implements NetToUsb {

    private WebLine webLine;

    public Adapter2(WebLine webLine) {
        this.webLine = webLine;
    }

    @Override
    public void handleRequest() {
        webLine.connect(); // 可以上网了
    }
}
// 客户端类  想上网 但是插不上网线
public class Computer {
    // 我们电脑需要连接上转接器才能上网
    public void net(NetToUsb adapter){
        // 上网的具体实现 找一个转接头
        adapter.handleRequest();
    }
    public static void main(String[] args) {
        // 电脑 适配器 网线
        WebLine WebLine = new WebLine();
        Adapter2 adapter = new Adapter2(WebLine);
        Computer computer = new Computer();

        computer.net(adapter);
    }
}

优点

1、可以让任何两个没有关联的类一起运行。

2、提高了类的复用。

3、增加了类的透明度。

4、灵活性好。

缺点

1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

2、由于 Java 至多继承一个类,所以至多只能适配一个类,而且目标类必须是抽象类。

结构型 --- 装饰器模式

装饰器模式能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。

从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,

区别在于方式的不同,后者是在编译时静态地通过对原始类的继承完成,而前者则是在程序运行时通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。
 

1.实体类

  • Gift类和Showable接口
package com.wang.design.chapter7;

public interface Showable {
    void show();//展示礼物的方法
}

class Gift implements Showable{
    @Override
    public void show() {
        System.out.print("礼物");
    }
}

2.装饰器

  • 用来包装Gift的包装器体系
package com.wang.design.chapter7;

/**
 * Decorator为包装器父类
 */
public abstract class Decorator implements Showable{
    protected Showable show_obj;

    public Decorator(Showable show_obj){
        this.show_obj=show_obj;//注入被装饰对象
    }

    @Override
    public void show() {
        show_obj.show();//调用默认展示方法
    }
}

/**
 * 包装盒
 */
class BoxDecorator extends Decorator{
    public BoxDecorator(Showable show_obj){
        super(show_obj);
    }

    @Override
    public void show() {
        System.out.print("包装盒【");
        show_obj.show();
        System.out.print("】包装盒");
    }
}

/**
 * 蝴蝶结
 */
class BowknotDecorator extends Decorator{
    public BowknotDecorator(Showable show_obj){
        super(show_obj);
    }
    @Override
    public void show() {
        System.out.print("蝴蝶结【");
        show_obj.show();
        System.out.print("】蝴蝶结");
    }
}

  • 客户端展示        
package com.wang.design.chapter7;

public class Client {
    public static void main(String[] args) {
        Showable gift=new BowknotDecorator(new BoxDecorator(new Gift()));
        gift.show();
    }
}

到这里,我们通过低耦合、高扩展的设计模式,成功实现了对一个类的包装。

行为型 --- 模板方法模式

模板方法模式非常类似于定制表格,设计者先将所有需要填写的信息头(字段名)抽取出来,再将它们整合在一起成为一种既定格式的表格,最后让填表人按照这个标准化模板去填写自己特有的信息,而不必为书写内容、先后顺序、格式而感到困扰。

具体地说,就是把所有子类通用的信息和行为抽象出来放在父类中,建立抽象方法或非抽象的通用方法,然后由子类去继承和实现。下面我以[上数学课]和[上英语课]为例,展示模板方法模式的代码实现。

  • 实体类
package com.wang.design.chapter12;

public abstract class Course {
    abstract void register();//选课
    abstract void homework();//做作业
    abstract void exam();//考试

    public void show(){//通用方法
        this.register();
        this.homework();
        this.exam();
    }
}

class Math extends Course{
    @Override
    void register() {
        System.out.println("=====数学课开课了=====");
    }

    @Override
    void homework() {
        System.out.println("=====完成数学课平时=====");
    }

    @Override
    void exam() {
        System.out.println("=====进行数学期末考试=====");
    }
}

class English extends Course{
    @Override
    void register() {
        System.out.println("=====英语课开课了=====");
    }

    @Override
    void homework() {
        System.out.println("=====完成英语课平时=====");
    }

    @Override
    void exam() {
        System.out.println("=====进行英语期末考试=====");
    }
}
  • 客户端测试一下
package com.wang.design.chapter12;

public class Client {
    public static void main(String[] args) {
        Course course=new Math();
        course.show();

        course=new English();
        course.show();
    }
}

行为型 --- 观察者模式

观察者模式可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。

在一般情况下,观察者总是处于非常忙碌的状态,因为它总是在一遍又一遍地轮询被观察者,直到被观察者的状态发生变化。这样做无疑会给系统带来很多额外的负担。观察者模式则反其道而行之,与其让观察者无休止地询问,不如让被观察者在状态发生变化之后,主动通知观察者前来访问。

本章以[商店卖货]为例。

  • 杂货铺
public class Shop {
    private String name;//商店名
    private List<String> handicraft,stationary,phone;//售卖种类:手工艺品、文具、手机
    private List<Buyer> buyers;//买家预定

    public Shop(String name){
        this.name=name;
        this.handicraft=new ArrayList<>();
        this.stationary=new ArrayList<>();
        this.phone=new ArrayList<>();
        buyers=new ArrayList<>();
        System.out.println(this.name+"开张了!");
    }

    public List<String> getHandicraft() {
        return handicraft;
    }
    public List<String> getStationary() {
        return stationary;
    }
    public List<String> getPhone() {
        return phone;
    }

    //商家登记
    public void register(Buyer buyer){
        this.buyers.add(buyer);
    }

    //进货
    public void purchaseHandicraft(String product){
        this.handicraft.add(product);
        notifyBuyers();
    }
    public void purchaseStationary(String product){
        this.stationary.add(product);
        notifyBuyers();
    }
    public void purchasePhone(String product){
        this.phone.add(product);
        notifyBuyers();
    }

    //通知买家
    public void notifyBuyers(){
        this.buyers.stream().forEach(b->b.inform(this));
    }
}
  • 买家类
public abstract class Buyer {
    protected String name;

    public Buyer(String name) {
        this.name = name;
    }

    //来自商家的到货通知
    public abstract void inform(Shop shop);
}

class HandicraftBuyer extends Buyer{
    public HandicraftBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getHandicraft();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入手工艺品=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}

class StatonaryBuyer extends Buyer{
    public StatonaryBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getStationary();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入文具=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}

class PhoneBuyer extends Buyer{
    public PhoneBuyer(String name) {
        super(name);
    }

    @Override
    public void inform(Shop shop) {
        List<String> products=shop.getPhone();
        if(products.size()!=0){
            System.out.println("====="+this.name+"买入手机=====");
            for (String str:products){
                System.out.println(str);
            }
            products.clear();
        }
    }
}
  • 客户端测试
public class Client {
    public static void main(String[] args) {
        Shop shop=new Shop("解忧杂货铺");
        Buyer handyBuyer=new HandicraftBuyer("手工艺品买手");
        Buyer phoneBuyer=new PhoneBuyer("手机买手");
        Buyer stationaryBuyer=new StatonaryBuyer("文具买手");

        //买家登记
        shop.register(handyBuyer);
        shop.register(phoneBuyer);
        shop.register(stationaryBuyer);

        //到货了
        shop.purchaseHandicraft("兵马俑摆件");
        shop.purchaseHandicraft("海螺捕梦网");
        shop.purchaseHandicraft("古风沙漏");
        shop.purchaseStationary("直尺");
        shop.purchaseStationary("书立");
        shop.purchaseStationary("订书机");
        shop.purchasePhone("华为荣耀20");
        shop.purchasePhone("华为荣耀20s");
    }
}
解忧杂货铺开张了!
=====手工艺品买手买入手工艺品=====
兵马俑摆件
=====手工艺品买手买入手工艺品=====
海螺捕梦网
=====手工艺品买手买入手工艺品=====
古风沙漏
=====文具买手买入文具=====
直尺
=====文具买手买入文具=====
书立
=====文具买手买入文具=====
订书机
=====手机买手买入手机=====
华为荣耀20
=====手机买手买入手机=====
华为荣耀20s

Process finished with exit code 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/700433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++练习】日期常见题型训练(5道编程题)

【C练习】日期题型训练 ①.日期累加②.日期差值③.打印日期④.求123...n(非正常方法)⑤.计算一年的第几天 ①.日期累加 解题思路&#xff1a; 1. 日期相加相减都要考虑2月的天数情况。 2.写一个可以获取每个月份天数的函数(要讨论闰年情况)。 3.当日期相加超过本月的最大天数时…

最新ai创作系统CHATGPT镜像系统源码+支持GPT4.0+支持ai绘画(MJ)+实时语音识别输入+免费更新版本

AI系统CHATGPT镜像程序源码支持GPT4支持ai绘画实时语音识别输入免费更新版本 一、AI创作系统二、系统介绍三、系统程序下载四、安装教程五、主要功能展示六、更新日志 一、AI创作系统 1、提问&#xff1a;程序已经支持GPT3.5、GPT4.0接口、支持新建会话&#xff0c;上下文记忆…

纷享销客获评“北京市用户满意企业”荣誉称号

近日&#xff0c;北京社会企业质量协会发布了2023年“北京市用户满意企业”名单&#xff0c;纷享销客再次成功入选并获得“北京市用户满意企业”称号。该评定活动由北京市用户满意认定办公室组织推进&#xff0c;北京质协用户评价中心认定实施&#xff0c;经过资料审核、第三方…

MySQL基础篇(day01,复习自用)

MySQL第一天 数据库概述概述RDBMS与非RDBMS关系型数据库设计规则 MySQL环境搭建MySQL的使用演示MySQL图形化管理工具目录结构 数据库概述 概述 RDBMS与非RDBMS 关系型数据库设计规则 MySQL环境搭建 卸载与安装此处省略 MySQL的使用演示 mysql -h 主机名 -P 端口号 -u 用户名…

vue项目搜索页面,点击搜索按钮button页面l刷新2次问题解决(亲测可用!)

vue遇到了一个奇葩的问题&#xff0c;接口什么都没调用但是点击搜索按钮button页面连续刷新两次 起因&#xff1a;写项目时 用原生写了一个按钮&#xff0c;点击页面会刷新 <button type"submit" click"search()"><i class"iconfont icon-s…

记录:win11+anaconda3 pip install 不装在anaconda3 里,非得装C盘

总结放到最前面&#xff1a;anconda 配置好后&#xff0c;pip install 会把包安装到c盘&#xff0c;不要修改site.py文件&#xff0c;问题出在windows用户权限上&#xff0c;修改windows11的文件夹的权限即可 如上&#xff0c;装好anconda之后&#xff0c;pip install 会把库装…

CSS中伪类详解和用法例子详解

文章目录 一、伪类介绍1.伪类选择器2.动态伪类3.结构伪类4.否定伪类5.状态伪类6.目标伪类 一、伪类介绍 1.伪类选择器 动态伪类作用:link链接没有被访问前的样式效果:visited链接被访问后的样式效果:hover鼠标悬停在元素上面时的样式效果:active点击元素时的样式效果&#xf…

flink-sql自定义rabbitmq connector

flink sql 自定义 rabbitmq connector 直接上代码 github 地址&#xff1a; https://github.com/liutaobigdata/flink-sql-rabbitmq-connector SourceFactory 代码 public class RabbitmqTableSourceFactory implements DynamicTableSourceFactory {private static final S…

进阶实战,接口自动化测试——requests文件上传/下载实战代码

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 如果需要发送文件…

record-4.网络

4、网络 技术栈&#xff1a; 1、计算机网络体系结构 OSI分层 &#xff08;7层&#xff09;&#xff1a;物理层、数据链路层&#xff08;网桥&#xff0c;交换机&#xff09;、网络层&#xff08;IP&#xff0c;ICMP&#xff0c;ARP&#xff09;、传输层&#xff08;TCP&…

springboot房屋管理系统

房屋管理系统 springboot房屋管理系统 java房屋管理系统 技术&#xff1a; 基于springboothtml房屋管理系统的设计与实现 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;8.x版本都可…

微服务-Gradle的入门和使用

对于一个新的工程拉下来&#xff0c;如果该工程用了gradle。需要学习一下gradle项目管理工具。我在本机macbook M1的环境下操作。 一、配置安装 下载Gradle&#xff1a; https://services.gradle.org/distributions/ 我下载了6.9版本的Gradle 下载好了以后&#xff0c;放到了…

elasticsearch学习篇:初识ES

一、什么是ES 1、基础概念 是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容es是elastic stack(ELK)的核心&#xff0c;负责存储、搜索、分析数据。 ELK包括以下内容&#xff1a; ELK被广泛应用在日志数据…

用python实现调用百度图片搜索的API

前言&#xff1a;这段代码是一个简单的图片爬虫程序它可以通过输入关键词&#xff0c;在百度图片中搜索相关图片&#xff0c;并返回一张随机的图片。代码使用Flask框架搭建了一个简单的Web应用&#xff0c;将用户输入的关键词作为参数传递给爬虫程序&#xff0c;然后从百度图片…

观察者模式(Observer)

别名 事件订阅者者&#xff08;Event-Subscriber&#xff09;监听者&#xff08;Listener&#xff09; 定义 观察者是一种行为设计模式&#xff0c;允许你定义一种订阅机制&#xff0c;可在对象事件发生时通知多个“观察”该对象的其他对象。 前言 1. 问题 假如你有两种类…

Linux vfs各种operation操作介绍

1.ext4文件系统定义的各种操作 //普通文件操作 const struct file_operations ext4_file_operations {.llseek ext4_llseek,.read_iter generic_file_read_iter,.write_iter ext4_file_write_iter,.unlocked_ioctl ext4_ioctl, #ifdef CONFIG_COMPAT.compat_ioctl …

【Python基础】- break和continue语句(文末送书4本)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

6 图像处理实现螺纹识别案例(matlab程序)

学习目的&#xff1a;学习识别案例掌握识别方法 2.代码 clear;clc;close all Iimread(luowen1.bmp); %读取螺纹图片 try Irgb2gray(I); %如果是RGB图&#xff0c;则转换成灰度图 catch end figure imshow(I) title(原图&#xff08;半边螺纹&#xff09;) f…

基于pyqt和卷积网络CNN的中文汉字识别

直接上效果演示图&#xff1a; 通过点击按钮可以实现在画板上写汉字识别和加载图片识别两个功能。 视频演示和demo仓库地址在b站视频001期&#xff1a; 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 所有代码展示&#xff1a; 十分的简洁&#xff0c;主…

【从零开始学习JAVA | 第二十六篇】泛型补充知识

目录 前言&#xff1a; 泛型的更多应用&#xff1a; 泛型类&#xff1a; 泛型方法&#xff1a; 泛型方法&#xff1a; 总结&#xff1a; 前言&#xff1a; 在上一篇文章中我们介绍了泛型的基础知识&#xff0c;也就是在创建集合的时候用到泛型&#xff0c;此时的泛型更多…