ดังที่ Gang of Four เขียน (อ้างอิงถึงหนังสือ “รูปแบบการออกแบบเชิงวัตถุ” โดยนักพัฒนาชั้นนำ 4 คน) จุดประสงค์ของรูปแบบนี้คือเพื่อกำหนดการพึ่งพาแบบหนึ่งต่อกลุ่มระหว่างวัตถุในลักษณะที่เมื่อ สถานะของวัตถุหนึ่งมีการเปลี่ยนแปลง ทุกสิ่งที่ขึ้นอยู่กับวัตถุนั้นจะได้รับแจ้งเกี่ยวกับสิ่งนี้และจะได้รับการอัปเดตโดยอัตโนมัติ รูปแบบนี้เรียกอีกอย่างว่า: ผู้อยู่ในอุปการะ (ผู้ใต้บังคับบัญชา) หรือ Publish-Subscribe (ผู้เผยแพร่ - ผู้สมัครสมาชิก) แต่ลองคิดดูโดยใช้ตัวอย่างของคริสตจักรคาทอลิก :) มีผู้ติดตามที่เชื่อในคำสอนของคริสตจักรแห่งนี้ เมื่อมีหลักคำสอนใหม่ๆ (หลักคำสอนบังคับ) และอื่นๆ ปรากฏขึ้น คนเหล่านี้ควรรู้เกี่ยวกับสิ่งเหล่านั้น แต่สิ่งนี้สามารถอธิบายในภาษาโปรแกรมโดยใช้รูปแบบนี้ได้อย่างไร? 1. เรามี “เสียงของคริสตจักร” (ตัวคริสตจักรเองหรือพระสันตปาปาเมื่อออกอากาศนอกโบสถ์) นั่นคือผู้ประกาศข่าวหรือหัวเรื่องที่ประกาศข่าวในคริสตจักร 2. มีนักบวชในคริสตจักรนี้ คือ ผู้สังเกตการณ์บางคนที่ต้องการทราบเหตุการณ์สำคัญๆ ดังนั้นวันนี้อาจมีนักบวช 1.3 พันล้านคน และพรุ่งนี้อาจมีไม่มากก็น้อย และคุณเพียงแค่ต้องแจ้งผู้ที่อยู่ในคริสตจักรนี้เท่านั้น (ไม่จำเป็นต้องไปรบกวนผู้ที่ไม่เชื่อพระเจ้าอีกต่อไป :) ดังนั้นทั้งหมดนี้จึงสามารถแสดงได้ดังนี้: มีคริสตจักรแห่งหนึ่งที่จะบอกฝูงแกะเกี่ยวกับบางสิ่งบางอย่างซึ่งคุณสามารถลงทะเบียนหรือปล่อยไว้ในทางตรงกันข้าม:
public interface Church {
void registerParishioner(Parishioner parishioner);
void removeParishioner(Parishioner parishioner);
void notifyParishioners();
}
มีคริสตจักรคาทอลิกบางแห่งที่นำวิธีการเหล่านี้ไปใช้ ตลอดจนข่าวสารและรายชื่อบุคคลที่ควรเผยแพร่ข่าวนี้:
public class CatholicChurch implements Church {
private List<parishioner> parishioners;
private String newsChurch;
public CatholicChurch() {
parishioners = new ArrayList<>();
}
public void setNewsChurch(String news) {
this.newsChurch = news;
notifyParishioners();
}
@Override
public void registerParishioner(Parishioner parishioner) {
parishioners.add(parishioner);
}
@Override
public void removeParishioner(Parishioner parishioner) {
parishioners.remove(parishioner);
}
@Override
public void notifyParishioners() {
for (Parishioner parishioner : parishioners)
parishioner.update(newsChurch);
}
}
มีนักบวชที่สามารถเข้าหรือออกจากโบสถ์ได้ (เพื่อให้รหัสง่ายขึ้นเราจะอนุญาตให้เขาเข้าเท่านั้น :)
public class Parishioner {
private String name;
public Parishioner(String name, Church church) {
this.name = name;
church.registerParishioner(this);
}
void update(String newsChurch) {
System.out.println(name + "узнал новость: " + newsChurch);
}
}
มันจะทำงานอย่างไร:
public class Main {
public static void main(String[] args) {
var catholicChurch = new CatholicChurch();
new Parishioner("Мартин Лютер", catholicChurch);
new Parishioner("Жан Кальвин", catholicChurch);
catholicChurch.setNewsChurch("Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года");
}
}
และผลลัพธ์ของโปรแกรม:
Мартин Лютер узнал новость: Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года
Жан Кальвин узнал новость: Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года
เหล่านั้น. ทันทีที่มีข่าวปรากฏในคริสตจักร ทุกคนที่อยู่ในกลุ่มสมาชิกที่ลงทะเบียนของคริสตจักรนี้จะได้รับแจ้งเกี่ยวกับเรื่องนี้ อะไรคือข้อเสียของการดำเนินการนี้: 1. ประการแรก อินเทอร์เฟซที่คุณสามารถลงทะเบียนและรับข่าวสารอาจไม่เกี่ยวข้องกับคริสตจักรแห่งนี้เท่านั้น (อาจจำเป็น) ดังนั้นจึงเป็นไปได้ที่จะย้ายสิ่งนี้ไปยังอินเทอร์เฟซที่สังเกตได้แยกต่างหากทันที 2. เช่นเดียวกันนี้สามารถทำได้กับนักบวชในคริสตจักร กล่าวคือ ย้ายวิธีการอัพเดตไปยังอินเทอร์เฟซที่แยกจากกัน และนำไปใช้กับนักบวชที่ต้องการ จากนั้นวิธีนี้จะสามารถใช้ได้โดยทั่วไปไม่ใช่โดยนักบวชของคริสตจักรคาทอลิก แต่โดยผู้ที่อาศัยอยู่ในการดำรงอยู่ของเอลฟ์ (หมายถึงขบวนการ "ถนนสู่ยูนิคอร์น") เหล่านั้น. สร้างอินเทอร์เฟซผู้สังเกตการณ์ด้วยวิธีการอัปเดต จะเกิดอะไรขึ้นในที่สุด:
interface Observable {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
public class CatholicChurch implements Observable {
private List<observer> parishioners;
private String newsChurch;
public CatholicChurch() {
parishioners = new ArrayList<>();
}
public void setNewsChurch(String news) {
this.newsChurch = news;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
parishioners.add(o);
}
@Override
public void removeObserver(Observer o) {
parishioners.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : parishioners)
o.update(newsChurch);
}
}
interface Observer {
void update (String news);
}
public class Parishioner implements Observer {
private String name;
public Parishioner(String name, Observable o) {
this.name = name;
o.registerObserver(this);
}
@Override
public void update(String news) {
System.out.println(name + " узнал новость: " + news);
}
}
ดังนั้น: เราได้ "ลดการเชื่อมต่อ" ระหว่างคริสตจักรและนักบวชซึ่งโดยธรรมชาติแล้วจะดีเฉพาะในการเขียนโปรแกรมเท่านั้น :) หัวข้อ (คริสตจักรคาทอลิก) มีเพียงรายชื่อผู้ฟัง (นักบวช) และเมื่อได้รับข่าว (การเปลี่ยนแปลง) ออกอากาศข่าวนี้ แก่ผู้ฟัง ตอนนี้คุณสามารถสร้างองค์กรอื่นๆ (เช่น โบสถ์โปรเตสแตนต์) และเผยแพร่ข่าวไปยังผู้ฟัง "ของคุณ" คุณต้องคำนึงด้วยว่าข้อมูลคลาส 2 (แม่นยำยิ่งขึ้นคือคลาส Observable และอินเทอร์เฟซ Observer) มีอยู่ในแพ็คเกจ java.util java แต่ตอนนี้ข้อมูลเหล่านั้นเลิกใช้แล้วด้วย java 9 (https://docs.oracle com/en/java/javase/15/ docs/api/java.base/java/util/Observable.html): เลิกใช้แล้ว คลาสนี้และอินเทอร์เฟซผู้สังเกตการณ์เลิกใช้แล้ว โมเดลเหตุการณ์ที่ Observer และ Observable รองรับนั้นค่อนข้างจำกัด ลำดับของการแจ้งเตือนที่ส่งโดย Observable ไม่ได้ระบุ และการเปลี่ยนแปลงสถานะไม่ได้อยู่ในการโต้ตอบแบบตัวต่อตัวกับการแจ้งเตือน หากต้องการโมเดลเหตุการณ์ที่สมบูรณ์ยิ่งขึ้น ให้พิจารณาใช้แพ็กเกจ java.beans สำหรับการส่งข้อความที่เชื่อถือได้และเป็นระเบียบระหว่างเธรด ให้พิจารณาใช้หนึ่งในโครงสร้างข้อมูลที่เกิดขึ้นพร้อมกันในแพ็คเกจ java.util.concurrent สำหรับการเขียนโปรแกรมสไตล์สตรีมเชิงโต้ตอบ โปรดดู Flow API ดังนั้นจึงไม่จำเป็นต้องใช้มัน และคุณสามารถใช้สิ่งอื่นแทนได้ แต่แก่นแท้ของรูปแบบจะไม่เปลี่ยนแปลง ตัวอย่างเช่น ลองใช้ PropertyChangeListener (เพื่อไม่ให้เขียนคลาสที่ไม่จำเป็นซึ่งได้ถูกเขียนไปแล้ว) จากแพ็คเกจ java.beans มาดูกันว่ามันจะเป็นอย่างไร: วิชา:
public class CatholicChurch {
private String news;
// используя support мы можем добавлять or удалять наших прихожан (слушателей)
private PropertyChangeSupport support;
public CatholicChurch() {
support = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
support.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
support.removePropertyChangeListener(pcl);
}
public void setNews(String value) {
support.firePropertyChange("news", this.news, value);
this.news = value;
}
}
และคลาสผู้ฟัง:
public class Parishioner implements PropertyChangeListener {
private String name;
public Parishioner(String name) {
this.name = name;
}
public void propertyChange(PropertyChangeEvent evt) {
this.setNews((String) evt.getNewValue());
}
public void setNews(String news) {
System.out.println(name + " узнал новость: " + news);
}
}
หากเรารันโค้ดต่อไปนี้:
public static void main(String[] args) {
CatholicChurch observable = new CatholicChurch();
observable.addPropertyChangeListener(new Parishioner("Мартин Лютер"));
observable.addPropertyChangeListener(new Parishioner("Жан Кальвин"));
observable.setNews("Дева Мария имеет непорочное зачатие... булла Ineffabilis Deus... 8 декабря 1854 года Папа Пий IX");
observable.setNews("Папа непогрешим... не всегда конечно, а только когда транслирует учение церкви ex cathedra... Первый Ватиканский собор 1869 год");
}
เราได้รับผลลัพธ์ดังต่อไปนี้:
มาร์ติน ลูเทอร์ ทราบข่าว: พระแม่มารีเป็นผู้ปฏิสนธินิรมล... วัว Ineffabilis Deus... 8 ธันวาคม พ.ศ. 2397 สมเด็จพระสันตะปาปาปิอุสที่ 9 จอห์น คาลวินทราบข่าว: พระแม่มารีเป็นผู้ปฏิสนธินิรมล... กระทิง Ineffabilis Deus ... 8 ธันวาคม 1854 สมเด็จพระสันตะปาปาปิอุสที่ 9 มาร์ติน ลูเทอร์ ทราบข่าว: สมเด็จพระสันตะปาปาไม่มีข้อผิดพลาด... ไม่แน่นอนเสมอไป แต่เฉพาะเมื่อเขาถ่ายทอดคำสอนของคริสตจักร อดีตมหาวิหาร... สภาวาติกันครั้งแรก พ.ศ. 2412 จอห์น คาลวิน เรียนรู้ ข่าว: สมเด็จพระสันตะปาปาไม่มีข้อผิดพลาด... ไม่แน่นอนเสมอไป แต่เฉพาะเมื่อเขาถ่ายทอดคำสอนของคริสตจักร อดีตมหาวิหาร... สภาวาติกันครั้งแรก 1869
GO TO FULL VERSION