初识设计模式 – 观察者模式

观察者设计模式(Observer Design Pattern)的别名有很多,如发布 – 订阅(Publish/Subscribe)模式、模型 – 视图(Model/View)模式、源 – 监听(Source/Listener)模式或从属者(Dependents)模式。

无论是何种名称,其意图都是在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

其主要解决了一个对象状态改变之后给其他对象通知的问题,而且考虑到易用性和低耦合,保证高度的协作性。

典型实现

在发布 – 订阅模式当中,观察者就是订阅者,一般是需要定义一个抽象的观察者接口,其代码示例如下:

public interface Observer {
    void update();
}

具体的观察者类也很简单,只需要简单实现接口即可,其代码示例如下:

public class ConcreteObserver implements Observer {
    // 实现响应方法
    public void update() {
        // 具体响应代码
    }
}

定义完订阅者,其次就是发布者,通常是将之定义为一个抽象的目标类,其代码示例如下:

public abstract class Subject {
    // 定义一个观察者集合存储所有观察者对象
    protected List observers = new ArrayList<>();

    // 注册方法,用于向观察者集合增加一个观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 注销方法,用于向观察者集合删除一个观察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 声明抽象的通知方法,需要由子类具体实现
    abstract void notifyObservers();
}

根据不同的场景定义不一样的具体目标类,如下是一种代码示例:

public class ConcreteSubject extends Subject {
    // 实现具体的通知方法
    @Override
    public void notifyObservers() {
        // 遍历观察者集合,调用每一个观察者的响应方法
        for (Observer ob : observers) {
            ob.update();
        }
    }
}

观察者模式的主要优点如下:

  • 观察者模式可以实现表示层和数据逻辑层的分离
  • 观察者模式在观察目标和观察者之间建立了一个抽象的耦合
  • 观察者模式支持广播通信,简化了一对多系统设计的难度
  • 增加新的具体观察者无需修改原有代码,符合开闭原则

观察者模式的主要缺点如下:

  • 如果目标对象的观察者有很多,将所有的观察者都通知到会非常耗时
  • 如果目标对象和观察者存在循环依赖,观察目标会触发它们之间进行循环调用,最终导致系统崩溃

观察者模式的适用场景如下:

  • 将具有依赖关系的两种抽象模型独立出来,使它们可以独立地改变和复用
  • 一个对象的改变将导致一个或多个其他对象也发生改变,但并不知道具体有多少对象将发生改变,也不知道这些对象是谁
  • 可以使用观察者模式创建一种链式触发机制

在 JDK 的 java.util 包中,提供了 Observer 接口和 Observable 类,它们构成了 JDK 对观察者模式的支持。

如下是 Observer 接口的源码:

@Deprecated(since = "9")
public interface Observer {
    void update(Observable o, Object arg);
}

如下是 Observable 类的部分源码:

@Deprecated(since = "9")
public class Observable {
    private boolean changed = false;
    private Vector obs;

    public Observable() {
        obs = new Vector<>();
    }

    // 注册观察者,线程安全
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        // 注册时去重
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    // 注销观察者,线程安全
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    // 通知观察者,无参数模式
    public void notifyObservers() {
        notifyObservers(null);
    }

    // 通知观察者,带参数模式
    public void notifyObservers(Object arg) {
        // 保存观察者的状态,备忘录模式的简单应用
        Object[] arrLocal;

        // 获取观察者时候锁住
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        // 逐一调用观察者的处理方法
        for (int i = arrLocal.length - 1; i >= 0; i--)
            ((Observer) arrLocal[i]).update(this, arg);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

需要注意的是,在 JDK 9 之后已经不推荐使用 Observer 接口和 Observable 类。

Original: https://www.cnblogs.com/fatedeity/p/16797903.html
Author: 程序员翔仔
Title: 初识设计模式 – 观察者模式



相关阅读

Title: 02-scrapy项目的创建基本使用

1、创建Scrapy项目

首先安scrapy
命令:sudo apt-get install scrapy 或者:pip install scrapy

创建scrapy项目的命令:

scrapy startproject +<项目名字>
&#x793A;&#x4F8B;&#xFF1A;scrapy startproject myspider
</项目名字>

生成的目录和文件结果如下:

初识设计模式 - 观察者模式

2、创建爬虫

命令:在项目路径下执行:scrapy genspider +

示例(以腾讯招聘网站为例):

cd myspider
scrapy genspider tencent careers.tencent.com

生成的目录和文件结果如下:

初识设计模式 - 观察者模式

3、完善spider

完善spider即通过方法进行数据的提取等操作
在/myspider/myspider/spiders/tencent.py中修改内容如下:

import scrapy

class TencentSpider(scrapy.Spider):

    name = 'tencent'

    allowed_domains = ['careers.tencent.com']

    start_urls = ['https://careers.tencent.com/search.html?keyword=python']

    def parse(self, response):

        info = response.xpath('//div[4]/div/a[@class="item-link"]/text()')
        print(info)

        info_list = response.xpath('//div//a[@class="recruit-list-link"]')
        for info in info_list:

            item = {}

            item['work_name'] = info.xpath('.//h4/text()').extract_first()
            item['work_duty'] = info.xpath('.//p[@class="recruit-text"]/text()').extract_first()
            print(item)
            yield item

注意:

  1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
  2. extract() 返回一个包含有字符串的列表
  3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
  4. spider中的parse方法必须有,是spider的一个方法。 被调用时,每个初始URL完成下载后生成的Response 对象将会作为唯一的参数传递给该函数。该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象
  5. 需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
  6. 启动爬虫的时候注意启动的位置,是在项目路径下启动

4、利用管道pipeline来处理(保存)数据

4.1 对itcast爬虫进行修改完善
在爬虫parse()函数中最后添加:

yield item

使用yield原因:

  • 让整个函数变成一个生成器,有什么好处呢?
  • 遍历这个函数的返回值的时候,挨个把数据读到内存,不会造成内存的瞬间占用过高
  • python3中的range和python2中的xrange同理

注意:yield能够传递的对象只能是:BaseItem,Request,dict,None

4.2 修改pipelines.py文件

class MyspiderPipeline:

    def process_item(self, item, spider):
        print(item)
        return item

4.3 在settings.py设置开启pipeline

ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
}

4.4 在settings.py设置关闭robots协议

    ROBOTSTXT_OBEY = False

4.5 在settings.py设置USER_AGENT

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko'

5、运行scrapy

命令:在项目目录下执行scrapy crawl +

Original: https://blog.csdn.net/s_daqing/article/details/115408865
Author: s_daqing
Title: 02-scrapy项目的创建基本使用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/363365/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总