The following is an example of two related aspects, subject and observer, which have to be used in tandem. Objects of classes with a subject aspect may be observed by a set of objects with an observer aspect. An observer object subscribes to certain events in the subject object, events that typically change the state of the subject object. In a case of such an event, the observer will be notified.
The types of these subject and observer aspects are defined by classes, in a module ObserverPattern
:
ObserverPattern: obj
class Subject:
observers: obj Set(#Observer)
subscribe(obs: ref Observer):
observers.insert(obs)
publish:
observers.scan
current.notify(this(Subject))
inner(Subject)
class Observer:
class ObservedSubject:< Subject
notify(theSubject: ref ObservedSubject):<
inner(notify)
Subjects have a set observers
of Observer
objects; the method subscribe
makes an Observer
object an element of this set; publish
is called in case the observeres should be notified about changes, so it will for each of the observers call their notify
.
+++ måske bullet opstilling og/eller en figur – helt afgørende at man forstår denne her.
+++Forslag: Subject and observer works this way:
- Given a
Subject
object denoted bysubjectToBeObserved
, anObserver
objectanObserver
subscribes to events in the subject by invokingsubjectToBeObserved.subscribe(anObserver)
. SeveralObserver
objects may subscribe to the sameSubject
object; subscribing implies that the observer is included the theobservers
set. - In case of an event that changes the state of the
Subject
object in such a way that its observers should get to know, theSubject
object invokes itspublish
method: this will for each of the observers in theobservers
set call itsnotify
method.
In order for an Observer
to know which Subject
notifies it, the notify
method has a reference parameter, theSubject
, that denotes the actual Subject
:
Note that the type of the theSubject
parameter is the virtual class ObservedSubject
. Typically a subject is an object of not just the class Subject
, but rather of a subclass of Subject
. ObservedSubject
is therefore constrained by Subject
.
A scenario that calls for these aspects is the following: Every day the bank checks if there has been some suspicious transactions on accounts, and in case, both a special alarm part of the bank and the customer are notified of the event. This is done by giving each account a subject aspect and both alarm and each customer an observer aspect.
Account
objects have a subject aspect by means of an object data-item asSubject
, while Customer
objects and the alarm
object each has an asObserver
data-item object. However, it is not enough to type these object data-items with Subject
or Observer
as illustrated below:
class Account(owner: ref Customer):
-:-
asSubject: obj Subject
class Customer(id: var Integer):
-:-
asObserver: obj Observer
alarm: obj
asObserver: obj Observer
The type of asSubject
object cannot just be Subject
, as it has to report things about the Account
-object. We therefore define a subject aspect specifically for Account
objects:
class AccountSubject(whichAccount: ref Account): Subject
issueWithTransactions:
"There is an issue with the account of: ".print
whichAccount.owner.print
inner(AccountSubject)
This class is used to define the asSubject
in Account
:
class Account(owner: ref Customer):
-:-
asSubject: obj AccountSubject(this(Account))
subscribe(alarm.asObserver)
subscribe(owner.asObserver)
Recall that an object defined by obj
is generated and executed as part of the generation of the object in which the object is. So, each time an Account
object is generated, the asSubject
object is generated and executed, with the implication that the alarm
and the owner
are subscribed as observers of this account. Note that the parameters to the invocation of subscribe
are not references to alarm
or owner
, but rather to their asObserver
aspect objects. +++ tror generelt at flere figurer vil være en god ide.
In a similar way, the asObserver
object data-items of Customer
and alarm
are defined subclassed from Observer
:
class Customer(name: var String):
asObserver: obj Observer
class ObservedSubject:: AccountSubject
notify::
theSubject.issueWithTransactions
...
alarm: obj
asObserver: obj Observer
class ObservedSubject:: AccountSubject
notify::
theSubject.issueWithTransactions
The virtual class ObservedSubject
in Observer
represents the type of the aspect of observed subjects. In general, i.e. in class Observer
, this is constrained to be subclassed from Subject
, while in the above code, ObservedSubject
is said to be (::
) AccountSubject
, as this is the (super)type of the observed aspects.
As mentioned, the scenario for the Accounts
being subject for observation, and for Customer
and alarm
to be observers is that the bank on regular basis go through all its account to find if there has been any issue with the transactions on accounts. This may be that the transactions form a suspicious pattern, e.g. because the account has been hacked.
Money mule example
As a simple example we consider the case of money mules. A money mule is someone who transfers (typically illegally acquired) money on behalf of someone else. Money mules can move money in various ways, including through bank accounts.
Some money mules know they are supporting criminal enterprises; others are unaware that they are helping criminals profit.
Money mules often receive a commission for their service, or they might provide assistance because they believe they have a trusting or romantic relationship with the individual who is asking for help.
Acting as a money mule is illegal, therefore the customer is also included as an observer.
For the purpose of simplicity we assume that money muling takes place in one day, i.e. there is a deposit of a certain amount and a transfer of the same amount to another account on the same day. This activity may not be money muling, but it is still worth being reported as an issue. In real life banking there has to be a pattern of this pair of deposit/transfer.
From before we have the class Transaction
:
class Transaction(
what: var String,
whichDate: var Date,
whichTime: var Time.Hours,
whichAmount: var Real):
...
For the purpose of checking illegal transactions, we define a special class of transactions:+++ why is this needed?
class TransferTransaction(from, to: ref Account): Transaction
... +++ nothing here?
In addition we add the transferTo
method to class Account
, and as we just consider transaction for one day, we also introduce a new set of transactions:
class Account(owner: ref Customer):
-:-
transferTo(amount: var Real, to: ref Account):
to.deposit(amount)
transactionsOfToday.insert(
TransferTransaction(
"transferTo", clock.today, clock.now, amount, this(Account),to))
transactionsOfToday: obj Set(Transaction)
Account
will for this purpose also have the method checkTransactions
: +++ nu er der lidt kompliceret
class Account(owner: ref Customer):
-:-
checkTransactions:
-- find if there is a deposit and a transfer of the same amount
-- at the same day, a simple case of money muling
sameAmountSameDay -> b: var Boolean:
scan:
transactionsOfToday.scan
amount: var Real
if (current.what = "deposit") :then
amount := current.whichAmount
transactionsOfToday.scan
if (current.what = "transferTo") :then
if (current.whichAmount = amount) :then
b := true
leave(scan)
if (sameAmountSameDay) :then
asSubject.publish
Det vil være fint om der er et komplet eksempel som kan checkes af compileren og som kan køre. Kompleksiteten gør jo at man nemt overser noget!
The Bank
object (or some object within the Bank
) calls this method for each Account
object in the list of accounts:
...
accountsFile: obj OrderedList(#Account)
...
accountsFile.scan
current.checkTransactions
The following Sequence Diagram shows the sequence of method calls for a partial run.