查看原文
其他

用好这4个设计模式,完成Java中的大部分任务

学研妹 Java学研大本营 2024-01-02

学好Java设计模式,提高代码质量、提高开发效率、提高代码可读性和可理解性。

长按关注《Java学研大本营》,加入读者群,分享更多精彩

为什么使用设计模式

Java中的设计模式提供了一种通用的解决方案,可以帮助开发人员解决常见的软件设计问题。设计模式是一种被广泛接受的最佳实践,它已经被证明可以提高代码的可读性、可维护性和可扩展性。

在Java中,设计模式被广泛应用于软件开发,特别是在大型的、复杂的软件系统中。设计模式提供了一种在面对复杂问题时的组织代码的方法,使得代码更易于理解和维护。

使用设计模式还可以避免一些常见的错误,例如过度耦合、低内聚性和不必要的复杂性。设计模式可以使代码更加灵活,易于扩展和修改,同时还可以提高代码的重用性和可测试性。

Java中的设计模式可以帮助开发人员更好地组织和管理代码,提高代码的质量和可维护性,从而使软件开发更加高效和可靠。

1、单例模式

在这种模式中,你只能创建一个类的实例。即使你创建多个引用变量,它们都将指向同一个对象。

相当简单明了,对吧?但是如何实现这个目标呢?如何确保只创建一个对象?通过一个例子,你将会更好地理解。

让我们来看一个名为Probe的类,它有其实例变量和方法。

class Probe {
    // 实例变量

    // 重要方法
}

我们的Probe类不希望有多个对象存在,因此我们将其构造函数设为私有。

private Probe() 
    // Initialize variables here
}

现在,这个构造函数只能从类内部调用。创建一个静态方法getInstance()

这个方法接受Probe类的引用变量,并检查对象是否已经被实例化。如果没有,则创建一个新对象并返回给客户端。

private static Probe getInstance(Probe probe) {
    if(probe == null)
      probe = new Probe();

    return probe;
}

由于getInstance()是一个静态方法,它只能在类级别而不是对象级别调用。每次调用该方法时,它都返回相同的对象。因此,该模式能够阻止你创建多个对象。

单例模式用于只需要一个实例的对象。例如,用于注册表设置和日志记录的对象只需要一个实例,否则它们可能会导致意外的副作用。

2、观察者模式

假设你关注一个社交媒体页面。每当这个页面添加新帖子时,你都希望收到通知。

因此,当一个对象(页面)执行一个动作(添加一个帖子)时,另一个对象(关注者)会被通知。这种情况可以通过观察者模式来实现。

让我们创建一个Page类和一个Follower接口。页面可以有不同类型的关注者,如普通用户、招聘人员和官方人员。我们将为每个关注者类型创建一个类,并且所有这些类都将实现Follower接口。

在这里,Page类是主体,关注者是观察者类。观察者模式定义了主体和观察者之间的一对多关系。如果主体更改其状态(页面添加了一个新的帖子),则所有的观察者即关注者都会被通知。

Page类将具有以下方法:

registerFollower()``:此方法注册新的关注者。 notifyFollowers():此方法通知所有关注者页面有新帖子。 `getLatestPost()`和`addNewPost():页面上最新帖子的gettersetter方法。

另一方面,Follower接口只有一个方法`update()``,该方法将被实现此接口的关注者类型重写,也称为具体观察者。

interface Follower {
    public void update();
}

当主体需要通知观察者状态更改即有新帖子时,将调用update()方法。

让我们实现Page类即主体。

class Page {
    private ArrayList<Follower> followers;
    String latestPost;
    
    public Page() {
        followers = new ArrayList();
    }
    
    public void registerFollower(Follower f) {
        followers.add(f);
    }
    
    public void notifyFollowers() {
        for(int i = 0; i < followers.size(); i++) {
            Follower follower = followers.get(i);
            follower.update();
        }
    }
    
    public String getLatestPost() {
        return latestPost;
    }
    
    public void addNewPost(String post) {
        this.latestPost = post;
        notifyFollowers();
    }
    
}

在这个类中,我们有一个所有关注者的列表。当一个新的关注者想要关注页面时,它调用registerFollower()方法。latestPost保存页面添加的新帖子。

当添加新帖子时,将调用notifyFollowers()方法,它会遍历每个关注者并通过调用update()方法通知他们。

现在,让我们实现我们的第一种关注者——用户。

class User implements Follower {
    Page page;
    public User(Page page) {
        this.page = page;
        page.registerFollower(this);
    }
    public void update() {
        System.out.println("Latest post seen by a normal user: " + page.getLatestPost());
    }
}

当创建一个新的User对象时,它会选择要关注的页面并注册。当页面添加新帖子时,User通过它自己实现的update()方法得到通知。

让我们再创建两个类来关注页面。

class Recruiter implements Follower {
    String company;
    // Rest is the same as User class
}
class Official implements Follower {
    String designation;    
    // Rest is the same as User class
}

让我们在主类中测试这种模式。首先创建一个页面并添加一个新的帖子。

Page page = new Page();
page.addNewPost("I am feeling lucky!");

目前还没有人关注页面,所以不会通知任何人。现在,一个普通用户关注了该页面并添加了另一篇帖子。

User user = new User(page);
page.addNewPost("It's a beautiful day!");

现在,用户将收到通知并输出以下内容。

Latest post seen by a normal user: It's a beautiful day!

接下来,招聘人员和官方人员也关注了该帖子。

Recruiter recruiter = new Recruiter(page);
Official official = new Official(page);
page.addNewPost("Ready to go for a run!!");

所有三个关注者都会得到这个活动的通知。

Latest post seen by a normal user: Ready to go for a run!!
Latest post seen by a recruiter: Ready to go for a run!!
Latest post seen by an official: Ready to go for a run!!

3、策略模式

我们想要为一些船只添加潜水功能。继承和方法重写这两种方法都不能满足我们的需求。我们需要将正在更改的代码与已经存在的代码分离开来。唯一正在更改的部分是dive()行为,因此我们创建一个Diveable接口并创建另外两个实现它的类。

interface Diveable {
    public void dive();
}
class DiveBehaviour implements Diveable {
    public void dive() {
      // Implementation here
    }
}
class NoDiveBehaviour implements Diveable {
    public void dive() {
       // Implementation here
    }
}

现在,在Boat类中,创建一个接口的引用变量,并拥有一个执行performDive()方法,该方法调用这个dive()方法。

abstract class Boat {
    Diveable diveable;
    void sway() { ... }
    void roll() { ... }
    
    abstract void present();
    
    public void performDive() {
        diveable.dive();
    }
}

FishBoatDinghyBoat类不应该具有潜水行为,因此它们将继承NoDiveBehaviour类。我们来看看具体实现方法。

class FishBoat extends Boat {
    ...

    public FishBoat() {
        diveable = new NoDiveBehaviour();
    }
    ...
}

diveable引用变量实例化为NoDiveBehaviour对象时,FishBoat类从该类继承dive()方法。

对于新的SubBoat类,可以继承一个新的行为。

class SubBoat extends Boat {
    ...  
  
    public FishBoat() {
        diveable = new DiveBehaviour();
    }
    ...
}

现在,让我们测试功能

Boat fishBoat = new FishBoat();
fishBoat.performDive();

当调用performDive()时,它调用NoDiveBehaviour类的dive方法。

Boat subBoat = new SubBoat();
subBoat.performDive();

这会执行完全不同的操作,因为它调用实际的潜水行为。因此,我们的新船变成了潜艇并潜水了下去。

4、装饰器模式

有时候,你想要对你的功能做一些修改。但在这样做时,你需要确保不改变现有的功能。

我们以一个Car类为例,该类被两个类FordAudi扩展。它有一个build()方法以及其他重要的方法。这个方法是抽象的,因为每辆车都有自己的实现。

abstract class Car {
    abstract void build();
}
class Ford extends Car {
    public void build() {
        System.out.println("Ford built");
    }
}

class Audi extends Car {
    public void build() {
        System.out.println("Audi built");
    }
}

一切都运行正常。然而,客户想要进行一些修改,比如添加彩色车灯、添加尾翼或添加氮气。你将如何进行这些添加?

一种方法是为具有这些修改的汽车创建不同的子类,如AudiWithSpoilerFordWithNitrous等。但你可以看到这很快就会变成一个大问题。可能的组合数量是没有限制的。

另一种方法是将每个mod作为一个实例变量,并在所需的mod上调用build()。然而,对于每个新的mod,你都需要不断修改现有的代码,从而增加引入错误的可能性。

有一种更好、更灵活的方法。你可以为每个mod定义单独的类,并将你的汽车包装在该对象周围。这是什么意思?你很快就会明白。

创建一个扩展Car的CarModifications类,将这个类称为mod类

abstract class CarModifications extends Car {
    Car car;

    public CarModifications(Car car) {
        this.car = car;
    }
}

通过在CarModifications内创建一个Car对象,你将Car包装在mod类内。mod类是一个抽象类,并由另外三个类扩展:ColorLightSpoilerNitrous

class Spoiler extends CarModifications {
    public Spoiler(Car car) {
        super(car);
    }
    
    public void build() {
        car.build();
        addSpoiler();
    }
    
    void addSpoiler() {
        System.out.println("Spoiler built");
    }
}

这是一个特定的mod类,它包装了Car。它通过先构建汽车,然后向其添加一个尾翼来实现build()方法。另外两个mod类也有类似的实现。

现在,让我们测试这个模式。我们将创建一个Audi并为它添加一个尾翼。

Car audi = new Audi();
Car audiWithSpoiler = new Spoiler(audi);
audiWithSpoiler.build();

在创建Car对象之后,你可以使用相同的实例创建一个新的带有附加尾翼的Car对象。调用build()方法执行该模式,得到以下输出。

Audi built
Spoiler built

如果你想要一个带有氮气的车,再创建另一个Car对象。

Car audiWithMods = new Nitrous(audiWithSpoiler);
audiWithMods.build();

输出:

Audi built
Spoiler built
Nitrous built

推荐书单

《深入理解Java高并发编程》

《深入理解Java高并发编程》致力于介绍Java高并发编程方面的知识。由于多线程处理涉及的知识内容十分丰富,因此介绍时必须从Java层面的讲解一直深入到底层的知识讲解。为了帮助读者轻松阅读本书并掌握其中知识,本书做了大量基础知识的铺垫。在第1篇基础知识储备中,主要介绍计算机原理、并发基础、常见语言的线程实现、Java并发入门、JUC之Java线程池、JUC之同步结构、Java NIO详解等内容。在第2篇深入Java并发原理中,详细介绍了JUC包中所有使用的原子类的原理与源码实现;非常关键且容易出错的volatile关键字的原理,从Java、JVM、C、汇编、CPU层面对其进行详细讲解;synchronized在JVM中获取锁和释放锁的流程;JUC包的核心结构——AQS的原理与源码实现,通过逐方法、逐行的解释,帮助读者彻底掌握AQS中提供的获取锁、释放锁、条件变量等操作的实现与原理。最后,详细介绍了JVM中JNI的实现原理,将Java Thread对象中的所有方法在JVM层面的实现流程进行了详细描述,以帮助读者在使用这些方法时,知道底层发生了什么,以及发生异常时如何从容解决问题。

购买链接:https://item.jd.com/13523064.html

精彩回顾

5个Java开发者不可不知的编程库

5个强大的IntelliJ IDEA插件,提高你的生产力

6个Android开发者不可不知的设计模式

精通Java后台任务开发,使用Spring简化异步任务处理

5个强大的IntelliJ IDEA插件,提高你的编程效率

长按关注《Java学研大本营》,加入读者群,分享更多精彩
长按访问【IT今日热榜】,发现每日技术热点
继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存