Deo zbornika Uvod u softversko inženjerstvo
Posmatrač (projektni obrazac)
Posmatrač (observer pattern) je obrazac ponašanja koji služi da delovi programa posmatraju i reaguju na promene u drugom delu programa. Ovaj obrazac omogućava automatsko obaveštavanje zainteresovanih objekata o svim promenama koje se dešavaju posmatranom subjektu.
Model posmatrača se obično implementira kada subjekat želi da šalje poruke svojim posmatračima. Subjekt ne treba da zna ništa o tome kako rade posmatrači.
Posmatrač je jedan od najpoznatijih softverskih obrazaca i široko je rasprostranjen - Java ga je uključila u osnovnu biblioteku (java.util.Observer
), a C# ugradio direktno u jezik (ključna reč event
).
Uloge
Kod ovog obrasca postoje dve glavne uloge:
- subjekt ili izdavač (publisher), koji obaveštava posmatrače kada se desi nešto važno.
- posmatrači (observers) ili pretplatnici (subscribers), koji čekaju obaveštenje izdavača da bi preduzeli akciju.
Primena
Posmatrački obrazac ima široku primenu.
Recimo da dodajemo sistem dostignuća u našu igru. Model posmatrača omogućava jednom modulu da objavi da se nešto zanimljivo dogodilo, ne brinući ko prima obaveštenje. Na primer, u igricama neki karakteri (posmatrači) menjaju ponašanje nakon što je kraljica (subjekt) ubijena.
Takođe, događaji pregledača su primer posmatračkog obrasca. Pregledač objavljuje događaj svim funkcijama koje su pretplaćene na njega. Konačno, i Redux biblioteka primenjuje ovaj obrazac. Kad se stanje promeni, prikaz se ažurira u skladu s novim stanjem.
Često je potrebno imati više uporednih prikaza istih podataka, na primjer grafički i tabularni prikaz. Kad se stanje promijeni, svi prikazi moraju se ažurirati. Obrazac posmatrača omogućuje postojanje više prikaza jednog objekta, a kad se stanje objekta promijeni, svi prikazi automatski se ažuriraju.
Primer obrasca u JS-u
class Subject {
constructor() {
this.observers = []
}
addObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer)
}
notify() {
this.observers.forEach(observer => observer.update())
}
}
class Observer {
update() {
console.log('State updated!')
}
}
// upotreba
const subject = new Subject()
const observer1 = new Observer()
const observer2 = new Observer()
subject.addObserver(observer1)
subject.addObserver(observer2)
subject.notify() // 'State updated!' for both observers
Primer obrasca u C#
using System;
using System.Collections.Generic;
class Subject {
private List<IObserver> observers = new List<IObserver>();
public void AddObserver(IObserver observer) {
observers.Add(observer);
}
public void RemoveObserver(IObserver observer) {
observers.Remove(observer);
}
public void Notify() {
foreach (var observer in observers) {
observer.Update();
}
}
}
public interface IObserver {
void Update();
}
class ConcreteObserver : IObserver {
public void Update() {
Console.WriteLine("State updated!");
}
}
class Program {
static void Main() {
var subject = new Subject();
var observer1 = new ConcreteObserver();
var observer2 = new ConcreteObserver();
subject.AddObserver(observer1);
subject.AddObserver(observer2);
subject.Notify(); // 'State updated!' for both observers
}
}
Primer: render na promenu stanja
Recimo da imamo objekat store
, koji čuva stanje naše aplikacije. S druge strane, imamo header
i footer
, čije su render metode pretplaćene na stanje skladišta, i pozivaju se sa svakom njegovom izmenom:
const store = {
state: {},
subscribers: [],
subscribe(callback) {
this.subscribers.push(callback)
},
publish() {
this.subscribers.forEach(callback => callback())
},
setState(newState) {
this.state = newState
this.publish()
}
}
const header = {
render() {
console.log("Azurira se header...")
}
}
const footer = {
render() {
console.log("Azurira se footer...")
}
}
// render metode se pretplaćuju da posmatraju skladište
store.subscribe(header.render)
store.subscribe(footer.render)
// stanje skladišta se menja, usled čega se posmatrači pozivaju
store.setState({ ulogovan: true })
U navedenom primeru imamo:
- niz
subscribers
, koji sadrži povratne funkcije - metod
subscribe()
, koji dodaje pretplatnika u taj niz - metod
publish()
koji poziva sve pretplaćene funkcije
Svaki put kada se setuje novo stanje skladišta, biće pozvani svi pretplatnici pomoću metode publish
.
Literatura
- Angelina Njeguš, Obrasci projektovanja softvera, Univerzitet Singidunum, Beograd, 2023.