Date post: | 21-Feb-2017 |
Category: |
Software |
Upload: | foo-cafe-copenhagen |
View: | 134 times |
Download: | 3 times |
A taste of trygveJim Coplien
Former C++ Programmer & AuthorGertrud & Cope
Helsingør, Danmarkjcoplien@gmail
Pointers…
• DCI documentation & downloads:• http://fulloo.info
• trygve on GitHub:• https://github.com/jcoplien/trygve
My greatest contribution to computing is that I never invented a programming language.
– Jerry Weinberg
In 1972, Kay coined the term: “Object-Oriented
Programming”• In his 1972 paper the word “class” appears
only once• Objects: operational models, in the machine, to
extend the capabilities of the human mind• Classes came into Smalltalk ca. 1976 (from
Simula 67)• trygve: conceived to address the largest gaps
between current OOP and the benefits of the original vision, through DCI
G&C
We feel that a child is a "verb" rather than a "noun", an actor rather than an object; he is not a scaled-up pigeon or rat; he is trying to acquire a model of his surrounding environment in order to deal with it ... We would like to hook into his current modes of thought in order to influence him rather than just trying to replace his model with one of our own.
Alan Kay
G&C
But we *do* design
• What kinds of tools / notations / models do you use?
• “… a modeling language is what the language designers omitted to include in the programming languages” — Reenskaug
Java Programs as Models
public void setParentScope(final StaticScope scope) { parentScope_ = scope;}public List<ObjectDeclaration> objectDeclarations() { final List<ObjectDeclaration> retval = new ArrayList<ObjectDeclaration>(); for (final Map.Entry<String, ObjectDeclaration> objectDecl : objectDeclarationDictionary_.entrySet()) { retval.add(objectDecl.getValue()); } return retval;}public List<ClassDeclaration> classDeclarations() { final List<ClassDeclaration> retval = new ArrayList<ClassDeclaration>(); for (final Map.Entry<String, ClassDeclaration> classDecl : classDeclarationDictionary_.entrySet()) { retval.add(classDecl.getValue()); } return retval;}
• I can find only classes in Java code• A simplistic hierarchy• No objects• No networks of collaboration• No dynamics (because I can’t!!! By design!!!)
Class (domain) structure
Use case structure
Roles (applicati
on)
Class (domain) structure
trygve Programs as Models
Use case structure
Roles (applicati
on)
DCI Conceptualization:• objects• the roles the objects play• the classes to which they belong, and• the operational models of their interaction
class Range { public Range(int a, int b) { … } public int start() const { return start_ } public int end() const { return end_ }}
class Scanner { }
context SpellCheck { role [] Words { public void check() { Words[lastIndex].review } public void review() public void reviewAWord() } role Document { public int len() { return length() } private String wordStartingAt(int start) { … } … }}
Research Results• From Héctor Adrián Valdecantos, RIT• “to state with high statistical significance that the context element in a
DCI system plays an important role in code comprehension.”• “the correctness analysis shows that in general programmers following
the DCI-trygve approach perform better than programmers using the OO-java approach.“
But couldn’t you just…
Use multiple dispatchThat’s chooses one algorithm based on multiple types, rather than a sequencing of multiple algorithms based on multiple types
Use aspectsThe joinpoints are still dictated by the class structure
Use mix-insClose, but how do they talk to each other?
Just use objects (e.g., self)A good start, but not enough
Use multi-paradigm designUndesired decoupling and lack of cohesion
G&C
Action Between Objects
View
The programmer must consider the system:action
between objects
ModelView
Controller
Skip
G&C
Action Between Objects
View
Programmers’ objects interactwith objects previously conceived by other programmers
G&C
System Operations
While the specific interactions are emergent, the
form of the interactions is
designed.
This form lives within no single object or class.
Objects are an overly small
concept, conceived for the revenge of the nerds, each
owning their individual classes
G&C
System Operations
context TripReservation
}
{role JourneyEnd { City city() { … } ….
}role JourneyStart { City city() { … } ….}
role RouteMap { Path pfinder() { … } ….}
Teaching Actors their Scripts
context TripReservation { role JourneyStart { … } role JourneyEnd { … } public TripReservation(Object jStart, Object jEnd){ JourneyStart = jStart; JourneyEnd = jEnd }}
Contextualized Polymorphism
foo
foofoo
foo
foo
?
bar
bar
barbar
bar
?
sna
sna
snasna
sna
?
“Just trust the objects to do
the right thing and everything
will be fine.” — Smalltalk
“You believe in things you don’t understand, you may suffer.”
— Stevie Wonder
Where is the use case?
Where is the use case?
Skip
Contextualized Polymorphism
go
Roles: A new concept
Context: Another
new concept
Contextualized Polymorphism
bar sna
go
Data
Cont
ext
Inte
ract
ion
System Operation
s
Hoare’s Insight• “There are two ways of constructing a
software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.” — Tony Hoare
• The primary goal of trygve is to make system operation code readable
• It is in many ways a language for 7-year-olds
Smalltalk 80
Smalltalk 76
Smalltalk 72
Simula 67
C++
Java
trygve
DCI Squeak
Algol 68
C#
Marvin
self
Javascript
C
Garbage-collected,
single-hierarchy
Syntax (Worse is Better)
Object thinking over
classthink
D
Class-orientedProgramming
Object-OrientedProgramming
Perl
awk
…
The trygve language• Contexts: System use cases in code• The goal: readable code• System use case steps are demarcated along Role
boundaries• The Context chooses objects — “Role-players” — for
each Role (one-time binding)• Many forms of object can play each Role — duck-typed
through a contract specification• Role-players are dumb, and Role / instance contracts
should be simple and primitive
trygve building blocks• Declarations and expressions — and one value• Parser swallows most naive Java syntax• Contexts: use cases — mainly a set of Roles• Classes: the “domain model” (dumb): actor
DNA• Classify objects by how they are built
• Roles: scripts for the actors — stateless• Classify objects by how they act
• So this works:int fact(int n) { int retval = 1; if (n > 1) retval = n * fact(n - 1); return retval}
• but this is orthodox:int fact(int n) { return if (n <= 1) 1 else n * fact(n-1)}
Details• Semicolons optional• Classes, but few class-oriented features
(e.g., there is no protected)• Strongly type-checked at run-time-typed;
Roles are duck-typed• Rudimentary templates• No exceptions, RTTI, concurrency
More going on here than meets the eye
• The trygve language is a stepping stone to side-effect free programming
• States make it difficult for a Role-player to play several Roles (the MI problem)
• Stateless computation transcends the problem
Both class and Context instances can play Roles
• … in which case, we really don’t need classes any more
• They never were part of the OO vision
• The trygve language supports more or less arbitrary scope nesting (anything inside anything)
• Classes become truly operational models: exactly the Piagetian ideal that Kay strove for
On babies and bathwater
• Classes are still part of the business vocabulary
• They are at least part of the learned business mental model
• They are certainly part of the programmer mental model — and programmers are people, too
• So the “classless movement” is really a fad
Reflection• Even common inclusion polymorphism is overly
general — reflection takes it far outside human mental models
• trygve slices reflection with a precisely honed scimitar
• Reflection is the Turing Machine of types• It shows a lack of design discipline in the
articulation of a paradigm• See “Reflections on Reflection,” SPLASH 2013
keynote
Conclusion: Everybody Wins
• Good for humans…• Both cognitive & volitive models in code• Organizes, rather than suppresses, complexity
• … and software engineering• Incremental addition of new use cases• Reduced discovery cost• Separates rate-of-change shearing layers
Postlog• trygve is an open-source community
research effort — join it (GitHub jcoplien / trygve)
• Also a binary download and documentation at fulloo.info.
• We’ve been stuck gilding the lily and making much ado about nothing for far too long
• trygve does not aspire to be king: it exists only to combat ignorance and stimulate thinking & dialog
Other assorted goodies
• Templates (very minimal — just enough so people can use familiar Java containers)
• Very basic Frames, Panels, Events, Colors• Rudimentary InputStream & OutputStream
I/O• No exceptions (goal is readability)
Look, Ma, no semicolons
interface EventHandler { public void handleEvent(Event e)}
class MyPanel extends Panel { int XSIZE = 1000 int YSIZE = 600 public int xsize() { return XSIZE } public int ysize() { return YSIZE }
public MyPanel() { Panel() eventHandler_ = null frame_ = new Frame("Bouncy") frame_.add("Center", this) frame_.resize(XSIZE, YSIZE) frame_.setVisible(true) drawRect(0, 0, xsize(), ysize()) repaint() }
public boolean handleEvent(Event event) { boolean retval = true if (event.id == Event.MOUSE_MOVE) { if (eventHandler_ != null) { eventHandler_.handleEvent(event) } } return retval }
Everything’s an expression
context SpellCheck { role Utilities { public boolean isDelim(String c) const { return switch (c) { case "ø": case "Ø": case "æ": case "Æ": case "å": case "Å": false; break default: (c < "a" || c > "z") && (c < "A" || c > "Z") } } } . . . .
Duck-typed Role / Object Contracts
role ThePanel { public void drawCircle(int x, int y, int r) { fillOval(x+r, y+r, r, r) } public void drawPaddle(int xs, int ys, int h, int w) { drawRect(xs, ys, h, w) } public int maxX() { return xsize() } public int maxY() { return ysize() } public void setColor(Color c) { setForeground(c) } public void clearObjects() { removeAll() } public void clear() { setColor(new Color(227, 221, 240)); fillRect(0, 0, maxX() - 1, maxY() - 1 ) } } requires { void fillOval(int x, int y, int h, int w); void drawRect(int x, int y, int h, int w); void fillRect(int x, int y, int h, int w); int xsize(); int ysize(); void removeAll(); void setForeground(Color color) }
A Puzzleclass ArrayDupTest { public void test() { int [] intArray = new int[5]; for (int i = 0; i < 5; i++) { intArray[i] = i }
for (int i = 0; i < 5; i++) { System.out.println(intArray[i]) } }}
new ArrayDupTest().test()
Most class-oriented features have been
removed• protected
• super
• Class::
• ABCs• static (though it exists internally)
Can an object play more than one Role?
• In series, yes: that’s the whole idea• In parallel, no…
• One Context could instantiate another Context
• That Context could share Role-players with the original
• … unless it’s in the same Context so that there is no confusion
The Object Machine• Classes are run-time objects (but not in the
language)• Two scratch stacks: main, and event• Two activation record stacks• Uses Java GC + Context reference counting• Like Smalltalk in that everything is an
object
G&C Coplien — Lean Architecture
trygve Account Exampleclass Currency{ public Currency(double amount) { amount_ = amount.clone } public Currency +(Currency amount) { assert(false) return this; } public Currency -(Currency amount) { assert(false) return this; } public String name() const { assert(false); return "" } public String sign() const { assert(false); return "" } public double amountInEuro() const { assert(false); return 0.0 } public double amount() const { return amount_ }
G&C Coplien — Lean Architecture
trygve Account Example public String toString() const { return amountInEuro().toString() } public int compareTo(Currency other) { if (amount() > other.amount()) return 1 else if (amount() < other.amount()) return -1; return 0 }
private double amount_}
class Euro extends Currency { public Euro(double amount) { Currency(amount) } public Euro -(Currency amount) { return new Euro(amount() - amount.amountInEuro()) } public Euro +(Currency amount) { return new Euro(amount() + amount.amountInEuro()) } public String name() const { return "Euro"; } public String sign() const { return "€"; }
G&C Coplien — Lean Architecture
trygve Account Example public double amountInEuro() const { return amount() } public String toString() const { return amount().toString() }}
class Account{ public Account() { acct_ = 1234 } public String accountID() const { return acct_.toString() } public Currency availableBalance() const { assert(false); return null } public void increaseBalance(Currency amount) { assert(false) } public void decreaseBalance(Currency amount) { assert(false) } public void updateLog(String message, Date date, Currency amount) { assert(false) } private int acct_}
G&C Coplien — Lean Architecture
trygve Account Exampleclass CheckingAccount extends Account { public CheckingAccount() { availableBalance_ = new Euro(100.00) } public Currency availableBalance() const { return availableBalance_ } public void decreaseBalance(Currency c) { availableBalance_ = availableBalance_ - c } public void updateLog(String message, Date t, Currency c) const { System.out.print("account: ").print(accountID()) .print(" CheckingAccount::updateLog(\"").print(message) .print("\", ").print(t.toString()).print(", ") .print(c.toString()).print(“)") .println() } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c }
private Currency availableBalance_}
class SavingsAccount extends Account { public SavingsAccount() { availableBalance_ = new Euro(0.00) } public Currency availableBalance() const { return availableBalance_ }
G&C Coplien — Lean Architecture
trygve Account Example public void decreaseBalance(Currency c) { assert(c > availableBalance_); availableBalance_ = availableBalance_ - c }
public void updateLog(String logMessage, Date timeOfTransaction, Currency amountForTransaction) const { assert(logMessage.length() > 0); assert(logMessage.length() < MAX_BUFFER_SIZE); // assert(new Date() < timeOfTransaction); System.out.print("account: ").print(accountID()) .print(" SavingsAccount::updateLog(\"").print(logMessage) .print("\", ").print(timeOfTransaction.toString()) .print(", ").print(amountForTransaction.toString()) .print(“)") .println() } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c }
private Currency availableBalance_; private int MAX_BUFFER_SIZE = 256}
G&C Coplien — Lean Architecture
trygve Account Exampleclass InvestmentAccount extends Account{ public InvestmentAccount() { availableBalance_ = new Euro(0.00) } public Currency availableBalance() const { return availableBalance_ } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c } public void decreaseBalance(Currency c) { availableBalance_ = availableBalance_ - c; } public void updateLog(String s, Date t, Currency c) const { System.out.print("account: ").print(accountID()) .print(" InvestmentAccount::updateLog(\"") .print(s).print("\", ").print(t.toString()) .print(", ").print(c.toString()).print(")") .println() }
private Currency availableBalance_;}
G&C Coplien — Lean Architecture
trygve Account Exampleclass Creditor{ public Creditor(Account account) { account_ = account } public Account account() { return account_ } public Currency amountOwed() const { return new Currency(0.0) }
private Account account_}
class ElectricCompany extends Creditor{ public ElectricCompany() { Creditor(new CheckingAccount()) } public Currency amountOwed() const { return new Euro(15.0) }}
class GasCompany extends Creditor{ public GasCompany() { Creditor( new SavingsAccount()); account().increaseBalance(new Euro(500.00)) // start off with a balance of 500 } public Currency amountOwed() const { return new Euro(18.76) }}
G&C Coplien — Lean Architecture
trygve Account Examplecontext TransferMoneyContext{ // Roles
role AMOUNT { public Currency(double amount); public Currency +(Currency amount); public Currency -(Currency amount); public String name() const; public String sign() const; public double amountInEuro() const; public double amount() const; public String toString() const; public int compareTo(Currency other) } requires { Currency(double amount); Currency +(Currency amount); Currency -(Currency amount); String name() const; String sign() const; double amountInEuro() const; double amount() const; String toString() const; int compareTo(Currency other) }
role GUI { public void displayScreen(int displayCode) } requires { void displayScreen(int displayCode) }
G&C Coplien — Lean Architecture
trygve Account Example role SOURCE_ACCOUNT { public void transferTo() { // This code is reviewable and meaningfully testable with stubs! int SUCCESS_DEPOSIT_SCREEN = 10;
beginTransaction(); if (this.availableBalance() < AMOUNT) { endTransaction(); assert(false, "Unavailable balance") } else { this.decreaseBalance(AMOUNT); DESTINATION_ACCOUNT.increaseBalance(AMOUNT); this.updateLog("Transfer Out", new Date(), AMOUNT); DESTINATION_ACCOUNT.updateLog("Transfer In", new Date(), AMOUNT); } GUI.displayScreen(SUCCESS_DEPOSIT_SCREEN); endTransaction() } } requires { void decreaseBalance(Currency amount); Currency availableBalance() const; void updateLog(String msg, Date time, Currency amount) }
G&C Coplien — Lean Architecture
trygve Account Example role DESTINATION_ACCOUNT { public void transferFrom() { this.increaseBalance(AMOUNT); this.updateLog("Transfer in", new Date(), AMOUNT); } public void increaseBalance(Currency amount); public void updateLog(String msg, Date time, Currency amount) } requires { void increaseBalance(Currency amount); void updateLog(String msg, Date time, Currency amount) }
public TransferMoneyContext(Currency amount, Account source, Account destination) { SOURCE_ACCOUNT = source; DESTINATION_ACCOUNT = destination; AMOUNT = amount } public TransferMoneyContext() { lookupBindings() } public void doit() { SOURCE_ACCOUNT.transferTo() }
G&C Coplien — Lean Architecture
trygve Account Example private void lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup InvestmentAccount investmentAccount = new InvestmentAccount(); investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money SOURCE_ACCOUNT = investmentAccount; DESTINATION_ACCOUNT = new SavingsAccount(); DESTINATION_ACCOUNT.increaseBalance(new Euro(500.00)); // start it off with money AMOUNT = new Euro(30.00) }}
G&C Coplien — Lean Architecture
trygve Account Examplecontext PayBillsContext{ public PayBillsContext() { lookupBindings } role [] CREDITORS { } requires { Currency amountOwed() } stageprop SOURCE_ACCOUNT { public String accountID() const; public Currency availableBalance() const; public void increaseBalance(Currency amount) unused; public void decreaseBalance(Currency amount) unused; public void updateLog(String message, Date date, Currency amount) unused } requires { String accountID() const; Currency availableBalance() const; void increaseBalance(Currency amount); void decreaseBalance(Currency amount); void updateLog(String message, Date date, Currency amount) }
public void doit() { for (Creditor credit : CREDITORS) { // Note that here we invoke another use case TransferMoneyContext xfer = new TransferMoneyContext( credit.amountOwed(), SOURCE_ACCOUNT, credit.account()); xfer.doit() } }
G&C Coplien — Lean Architecture
trygve Account Example private void lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup InvestmentAccount investmentAccount = new InvestmentAccount(); investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money SOURCE_ACCOUNT = investmentAccount;
Creditor [] creditors = new Creditor [2];
creditors[0] = new ElectricCompany(); creditors[1] = new GasCompany();
CREDITORS = creditors }}
{ // Main
TransferMoneyContext aNewUseCase = new TransferMoneyContext(); aNewUseCase.doit();
PayBillsContext anotherNewUseCase = new PayBillsContext(); anotherNewUseCase.doit()}
G&C Coplien — Lean Architecture
C++ Account ExampleMany thanks to Felix Petriconi for bringing this up-to-date for C++14!
G&C Coplien — Lean Architecture
Account Example: Currency/* * Currency.h * * Created by James Coplien on 7/31/09. * Copyright 2009 Gertrud & Cope. All rights reserved. */
#ifndef _CURRENCY_H#define _CURRENCY_H
#include <string>
class Currency;
class CurrencyBase{ friend class Currency;public: CurrencyBase(); virtual ~CurrencyBase() = default; virtual CurrencyBase &operator+=(const Currency& amount) = 0; virtual CurrencyBase &operator-=(const Currency& amount) = 0; virtual CurrencyBase &operator=(const Currency& amount) = 0; virtual std::string name() const = 0; virtual std::string sign() const = 0; virtual double amountInEuro() const = 0; virtual std::string asString() const = 0;protected: unsigned referenceCount_;};
G&C Coplien — Lean Architecture
Account Example: Currencyclass Currency{public: Currency(); Currency(const Currency& amount); virtual Currency& operator=(const Currency& amount); explicit Currency(CurrencyBase *derivedClassThis); virtual ~Currency(); virtual Currency& operator+=(const Currency& amount); virtual Currency& operator-=(const Currency& amount); virtual std::string name() const; virtual std::string sign() const; virtual double amountInEuro() const; virtual std::string asString() const; friend bool operator==(const Currency& x, const Currency& y); friend bool operator!=(const Currency& x, const Currency& y); friend bool operator<(const Currency& x, const Currency& y); friend bool operator>(const Currency& x, const Currency& y); friend bool operator<=(const Currency& x, const Currency& y); friend bool operator>=(const Currency& x, const Currency& y);
G&C Coplien — Lean Architecture
Account Example: Currency friend std::ostream &operator<<(std::ostream& stream, const Currency& currency); private: CurrencyBase *actualCurrency_;};
class Euro: public CurrencyBase{ friend class Currency;public: explicit Euro(double amount = 0.0); virtual ~Euro() = default; operator Currency(); CurrencyBase *copy() const; Euro &operator+=(const Currency& amount) override; Euro &operator-=(const Currency& amount) override; Euro &operator=(const Currency& amount) override; std::string name() const override; std::string sign() const override; double amountInEuro() const override; std::string asString() const override;private: double amount_;};
#endif
G&C Coplien — Lean Architecture
Account Example: Currency/* * Currency.h * * Created by James Coplien on 7/31/09. * Copyright 2009 Gertrud & Cope. All rights reserved. */
#ifndef _CURRENCY_H#define _CURRENCY_H
#include <string>
class Currency;
class CurrencyBase{ friend class Currency;public: CurrencyBase(); virtual ~CurrencyBase() = default; virtual CurrencyBase &operator+=(const Currency& amount) = 0; virtual CurrencyBase &operator-=(const Currency& amount) = 0;
G&C Coplien — Lean Architecture
Account Example: Currency virtual CurrencyBase &operator=(const Currency& amount) = 0; virtual std::string name() const = 0; virtual std::string sign() const = 0; virtual double amountInEuro() const = 0; virtual std::string asString() const = 0;protected: unsigned referenceCount_;};
class Currency{public: Currency(); Currency(const Currency& amount); virtual Currency& operator=(const Currency& amount); explicit Currency(CurrencyBase *derivedClassThis); virtual ~Currency(); virtual Currency& operator+=(const Currency& amount); virtual Currency& operator-=(const Currency& amount); virtual std::string name() const; virtual std::string sign() const;
G&C Coplien — Lean Architecture
Account Example: Currency virtual double amountInEuro() const; virtual std::string asString() const; friend bool operator==(const Currency& x, const Currency& y); friend bool operator!=(const Currency& x, const Currency& y); friend bool operator<(const Currency& x, const Currency& y); friend bool operator>(const Currency& x, const Currency& y); friend bool operator<=(const Currency& x, const Currency& y); friend bool operator>=(const Currency& x, const Currency& y); friend std::ostream &operator<<(std::ostream& stream, const Currency& currency); private: CurrencyBase *actualCurrency_;};
G&C Coplien — Lean Architecture
Account Example: Currencyclass Euro: public CurrencyBase{ friend class Currency;public: explicit Euro(double amount = 0.0); virtual ~Euro() = default; operator Currency(); CurrencyBase *copy() const; Euro &operator+=(const Currency& amount) override; Euro &operator-=(const Currency& amount) override; Euro &operator=(const Currency& amount) override; std::string name() const override; std::string sign() const override; double amountInEuro() const override; std::string asString() const override;private: double amount_;};
#endif
G&C Coplien — Lean Architecture
Account Example: Currency/* * Currency.cpp * AgileBook * * Created by James Coplien on 7/31/09. * Copyright 2009 Gertrud & Cope. All rights reserved. * */
#include "Currency.h"
#include <sstream>
CurrencyBase::CurrencyBase(): referenceCount_(1){ }
Currency &Currency::operator+=(const Currency &amount) { *actualCurrency_ += amount; return *this;}
Currency &Currency::operator-=(const Currency &amount) { *actualCurrency_ -= amount; return *this;}
G&C Coplien — Lean Architecture
Account Example: CurrencyCurrency &Currency::operator=(const Currency &amount) { amount.actualCurrency_->referenceCount_++; if (--actualCurrency_->referenceCount_ <= 0) delete actualCurrency_; actualCurrency_ = amount.actualCurrency_; return *this;}
Currency::Currency(const Currency &amount) { (actualCurrency_ = amount.actualCurrency_)->referenceCount_++;}
Currency::Currency() : actualCurrency_(new Euro) {}
Currency::~Currency() { if (--actualCurrency_->referenceCount_ <= 0) delete actualCurrency_;}
std::string Currency::name() const{ return actualCurrency_->name();}
std::string Currency::sign() const{ return actualCurrency_->sign();}
G&C Coplien — Lean Architecture
Account Example: Currencybool operator<=(const Currency &x, const Currency &y) { return !(y < x);}
bool operator>=(const Currency &x, const Currency &y) { return !(x < y);}
Currency::Currency(CurrencyBase *derivedClassThis): actualCurrency_(derivedClassThis){}
Euro::operator Currency(){ return Currency(this->copy());}
CurrencyBase *Euro::copy() const{ return new Euro(amount_);}
Euro &Euro::operator+=(const Currency &amount) { amount_ += amount.amountInEuro(); return *this;}
G&C Coplien — Lean Architecture
Account Example: CurrencyEuro &Euro::operator-=(const Currency &amount) { amount_ -= amount.amountInEuro(); return *this;}
Euro &Euro::operator=(const Currency &amount) { amount_ = amount.amountInEuro(); return *this;}
Euro::Euro(double amount): amount_(amount){}
std::string Euro::name() const{ return "Euro";}
std::string Euro::sign() const{ return "€";}
double Euro::amountInEuro() const{ return amount_;}
G&C Coplien — Lean Architecture
Account Example: Currencystd::string Euro::asString() const { std::ostringstream stream; stream << amount_; return stream.str();}
G&C Coplien — Lean Architecture
Account Example: Currencybool operator<=(const Currency &x, const Currency &y) { return !(y < x);}
bool operator>=(const Currency &x, const Currency &y) { return !(x < y);}
Currency::Currency(CurrencyBase *derivedClassThis): actualCurrency_(derivedClassThis){}
Euro::operator Currency(){ return Currency(this->copy());}
CurrencyBase *Euro::copy() const{ return new Euro(amount_);}
Euro &Euro::operator+=(const Currency &amount) { amount_ += amount.amountInEuro(); return *this;}
Euro &Euro::operator-=(const Currency &amount) { amount_ -= amount.amountInEuro(); return *this;}
Euro &Euro::operator=(const Currency &amount) { amount_ = amount.amountInEuro(); return *this;}
Euro::Euro(double amount): amount_(amount){}
std::string Euro::name() const{ return "Euro";}
std::string Euro::sign() const{ return "€";}
double Euro::amountInEuro() const{ return amount_;}
std::string Euro::asString() const { std::ostringstream stream; stream << amount_; return stream.str();}
G&C Coplien — Lean Architecture
Account Example: Account/* * Account.h * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. * */
#ifndef _ACCOUNT_H#define _ACCOUNT_H
#include <string>#include "Currency.h"
class Account{public: Account(); std::string accountID() const; virtual void increaseBalance(const Currency&) = 0; virtual void decreaseBalance(const Currency&) = 0; private: int acct_;};
#endif
G&C Coplien — Lean Architecture
Account Example: Account/* * Account.cpp * AgileBook * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. * */
#include "Account.h"#include <sstream>
using namespace std;
namespace{ int accountCounter = 0;}
Account::Account() { acct_ = accountCounter++;}
string Account::accountID() const { string retval; std::stringstream s(retval); s << acct_; return retval;}
G&C Coplien — Lean Architecture
Account Example: CheckingAccount/* * CheckingAccount.h * * Created by James Coplien on 9/17/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#ifndef _CHECKINGACCOUNT_H#define _CHECKINGACCOUNT_H
#include "Account.h"#include "Currency.h"#include "TransferMoneyContext.h"#include "MyTime.h"#include <string>
class CheckingAccount: public Account, public TransferMoneyContext::TransferMoneySink<CheckingAccount> {public: CheckingAccount(); virtual ~CheckingAccount() = default;
// These functions can be virtual if there are // specialisations of CheckingAccount Currency availableBalance() const; void decreaseBalance(const Currency&) override; void increaseBalance(const Currency&) override; void updateLog(const std::string&, const MyTime&, const Currency&) const override;private: Currency availableBalance_;};
#endif
G&C Coplien — Lean Architecture
Account Example: CheckingAccount/* * CheckingAccount.cpp * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "CheckingAccount.h"#include "PayBillsContext.h"#include "Currency.h"#include "TransferMoneyContext.h"#include <iostream>
CheckingAccount::CheckingAccount(): availableBalance_(Euro(100.00)){}
Currency CheckingAccount::availableBalance() const{ std::cout << "CheckingAccount::availableBalance returns " << availableBalance_ << std::endl; return availableBalance_;}
void CheckingAccount::decreaseBalance(const Currency& c) { std::cout << "CheckingAccount::decreaseBalance(" << c << ")" << std::endl; availableBalance_ -= c;}
G&C Coplien — Lean Architecture
void CheckingAccount::updateLog(const string& message, const MyTime& t, const Currency& c) const { std::cout << "account: " << accountID() << " CheckingAccount::updateLog(\"" << message << "\", MyTime, " << c << ")" << std::endl;}
void CheckingAccount::increaseBalance(const Currency& c) { std::cout << "CheckingAccount::increaseBalance(" << c << ")" << std::endl; availableBalance_ += c;}
Account Example: CheckingAccount
G&C Coplien — Lean Architecture
Account Example: SavingsAccount/* * SavingsAccount.h * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#ifndef _SAVINGSACCOUNT_H#define _SAVINGSACCOUNT_H
#include "Account.h"#include "TransferMoneyContext.h"#include "MyTime.h"#include <string>
class SavingsAccount: public Account, public TransferMoneyContext::TransferMoneySink<SavingsAccount> {public: SavingsAccount(); SavingsAccount(Currency initialBalance): availableBalance_(initialBalance) { }private: // These functions can be virtual if there are // specialisations of SavingsAccount Currency availableBalance() const; void decreaseBalance(const Currency&) override; void increaseBalance(const Currency&) override; void updateLog(const std::string&, const MyTime&, const Currency&) const override;private: Currency availableBalance_;};
#endif
G&C Coplien — Lean Architecture
Account Example: SavingsAccount/* * SavingsAccount.cpp * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "SavingsAccount.h"#include "PayBillsContext.h"#include "TransferMoneyContext.h"
#include <iostream>#include <cassert>
using namespace std;
SavingsAccount::SavingsAccount(): availableBalance_(Euro(0.00)) { // no application code assert(availableBalance_ == Euro(0.0));}
Currency SavingsAccount::availableBalance() const { cout << "SavingsAccount::availableBalance returns " << availableBalance_ << std::endl; return availableBalance_;}
G&C Coplien — Lean Architecture
Account Example: SavingsAccountvoid SavingsAccount::decreaseBalance(const Currency& c) { cout << "SavingsAccount::decreaseBalance(" << c << ")" << endl; assert(c > availableBalance_); availableBalance_ -= c; assert(availableBalance_ > Euro(0.0));}
const unsigned MAX_BUFFER_SIZE = 256;
void SavingsAccount::updateLog(const string& logMessage, const MyTime& timeOfTransaction, const Currency& amountForTransaction) const { assert(logMessage.size() > 0); assert(logMessage.size() < MAX_BUFFER_SIZE); assert(MyTime("00:00:00.00 1970/1/1") < timeOfTransaction); cout << "account: " << accountID() << " SavingsAccount::updateLog(\"" << logMessage << "\", MyTime, " << amountForTransaction << ")" << endl;}
void SavingsAccount::increaseBalance(const Currency& c) { cout << "SavingsAccount::increaseBalance(" << c << ")" << endl; availableBalance_ += c;}
G&C Coplien — Lean Architecture
Account Example: InvestmentAccount/* * InvestmentAccount.h * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#ifndef _INVESTMENTACCOUNT_H#define _INVESTMENTACCOUNT_H
#include "Account.h"#include "Currency.h"#include "TransferMoneyContext.h"#include "MyTime.h"
class InvestmentAccount: public TransferMoneyContext::TransferMoneySource<InvestmentAccount>{public: InvestmentAccount(); Currency availableBalance() const; void increaseBalance(const Currency&) override; void decreaseBalance(const Currency&) override; void updateLog(const std::string&, const MyTime&, const Currency&) const override;private: Currency availableBalance_;};
#endif
G&C Coplien — Lean Architecture
Account Example: InvestmentAccount/* * InvestmentAccount.cpp * * Created by James Coplien on 9/2/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "InvestmentAccount.h"#include "PayBillsContext.h"#include "TransferMoneyContext.h"#include <string>#include <iostream>
InvestmentAccount::InvestmentAccount() : availableBalance_(Euro(0.00)) {}
Currency InvestmentAccount::availableBalance() const { std::cout << "InvestmentAccount::availableBalance returns " << availableBalance_ << std::endl; return availableBalance_;}
void InvestmentAccount::increaseBalance(const Currency &c) { std::cout << "InvestmentAccount::increaseBalance(" << c << ")" << std::endl; availableBalance_ += c;}
G&C Coplien — Lean Architecture
void InvestmentAccount::decreaseBalance(const Currency &c) { std::cout << "InvestmentAccount::decreaseBalance(" << c << ")" << std::endl; availableBalance_ -= c;}
void InvestmentAccount::updateLog(const std::string &s, const MyTime &, const Currency &c) const { std::cout << "account: " << accountID() << " InvestmentAccount::updateLog(\"" << s << "\", Time, " << c << ")" << std::endl;}
Account Example: InvestmentAccount
G&C Coplien — Lean Architecture
Account Example: Creditors/* * Creditor.h * * Created by James Coplien on 9/17/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#ifndef _CREDITOR_H#define _CREDITOR_H
#include "Currency.h"#include "MoneyPort.h"#include "TransferMoneyContext.h"
class Creditor{public: Creditor(TransferMoneyContext::MoneySink *account); virtual ~Creditor(); virtual TransferMoneyContext::MoneySink *account() { return account_; } virtual Currency amountOwed() const = 0;protected: TransferMoneyContext::MoneySink *account_;};
G&C Coplien — Lean Architecture
Account Example: Creditors
class ElectricCompany: public Creditor{public: ElectricCompany(); virtual ~ElectricCompany() = default; Currency amountOwed() const override;};
class GasCompany: public Creditor{public: GasCompany(); virtual ~GasCompany() = default; Currency amountOwed() const override;};
#endif
G&C Coplien — Lean Architecture
Account Example: Creditors/* * Creditor.cpp * * Created by James Coplien on 9/17/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "Creditor.h"#include "CheckingAccount.h"#include "SavingsAccount.h"
Creditor::Creditor(TransferMoneyContext::MoneySink *account): account_(account) {}
Creditor::~Creditor() { delete account_;}
ElectricCompany::ElectricCompany(): Creditor(new CheckingAccount()){}
Currency ElectricCompany::amountOwed() const{ return Euro(15.0);}
G&C Coplien — Lean Architecture
Account Example: CreditorsGasCompany::GasCompany(): Creditor(new SavingsAccount(Euro(500.00))) // for the demo{}
Currency GasCompany::amountOwed() const{ return Euro(18.76); // for the demo}
G&C Coplien — Lean Architecture
Account Example: Context/* * Context.h * * Created by James Coplien on 1/14/09. Updated to C++14 7/12/2016. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#ifndef _CONTEXT_H#define _CONTEXT_H
class Context {public: Context(); virtual ~Context();public: static Context *currentContext_;private: Context *parentContext_;};
#endif
G&C Coplien — Lean Architecture
Account Example: Context/* * Context.cpp * * Created by James Coplien on 1/14/09. Updated to C++14 7/12/2016. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#include "Context.h"
Context *Context::currentContext_ = nullptr;
Context::Context() { parentContext_ = currentContext_; currentContext_ = this;}
Context::~Context() { currentContext_ = parentContext_;}
G&C Coplien — Lean Architecture
Account Example: Transfer Money/* * TransferMoneyContext.h * * Created by James Coplien on 9/13/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
// TransferMoneyContext knows how to find the objects for a given// use case invocation. It associates those objects with// the roles they play in a use case of this type; and it// publishes those interface bindings for use by the// other Roles that participate in the use case (behind the// scenes).
#ifndef _XFERMONEYCONTEXT_H#define _XFERMONEYCONTEXT_H
#include "Account.h"#include "Context.h"#include "Currency.h"#include "MyExceptions.h"#include "Globals.h"#include "MoneyPort.h"#include <string>
class MyTime;
G&C Coplien — Lean Architecture
class TransferMoneyContext: public Context{public: // Roles class MoneySource: public MoneyPort, public Account { public: virtual ~MoneySource() = default; virtual void transferTo() = 0; virtual void decreaseBalance(const Currency&) = 0; virtual void updateLog(const std::string&, const MyTime&, const Currency& amount) const = 0; };
Account Example: Transfer Money
G&C Coplien — Lean Architecture
template <class RolePlayer> class TransferMoneySource: public MoneySource { public: TransferMoneySource() = default; virtual ~TransferMoneySource() = default; virtual void transferTo() override { // This code is reviewable and // meaningfully testable with stubs! beginTransaction(); if (SELF<RolePlayer>(this)->availableBalance() < AMOUNT<TransferMoneyContext>()) { endTransaction(); throw InsufficientFunds(); } else { SELF<RolePlayer>(this)->decreaseBalance(AMOUNT<TransferMoneyContext>()); RECIPIENT<TransferMoneyContext>()-> increaseBalance(AMOUNT<TransferMoneyContext>()); SELF<RolePlayer>(this)->updateLog("Transfer Out", DateTime(), AMOUNT<TransferMoneyContext>()); RECIPIENT<TransferMoneyContext>()->updateLog("Transfer In", DateTime(), AMOUNT<TransferMoneyContext>()); } GUI->displayScreen(SUCCESS_DEPOSIT_SCREEN); endTransaction(); } };
Account Example: Transfer Money
G&C Coplien — Lean Architecture
class MoneySink: public MoneyPort { public: virtual ~MoneySink() = default; virtual void increaseBalance(const Currency& amount) = 0; virtual void updateLog(const std::string&, const MyTime&, const Currency& amount) const = 0; }; template <class RolePlayer> class TransferMoneySink: public MoneySink { public: TransferMoneySink() = default; virtual ~TransferMoneySink() = default; virtual void transferFrom(const Currency& amount) { SELF<RolePlayer>(this)->increaseBalance(amount); SELF<RolePlayer>(this)->updateLog("Transfer in", DateTime(), amount); } };public: TransferMoneyContext(); TransferMoneyContext(const Currency& amount, MoneySource *src, MoneySink *destination); void doit(); TransferMoneyContext::MoneySource *sourceAccount() const; TransferMoneyContext::MoneySink *destinationAccount() const; Currency amount() const;
Account Example: Transfer Money
G&C Coplien — Lean Architecture
private: void lookupBindings(); MoneySource *sourceAccount_; MoneySink *destinationAccount_; Currency amount_;};
#endif
Account Example: Transfer Money
G&C Coplien — Lean Architecture
/* * TransferMoneyContext.cpp * * Created by James Coplien on 9/13/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "TransferMoneyContext.h"#include "InvestmentAccount.h"#include "SavingsAccount.h"
TransferMoneyContext::TransferMoneyContext() : Context() { lookupBindings();}
TransferMoneyContext::~TransferMoneyContext() {}
TransferMoneyContext::TransferMoneyContext(const Currency& amount, MoneySource *source, MoneySink *destination): Context() { amount_ = amount; sourceAccount_ = source; destinationAccount_ = destination;}
Account Example: Transfer Money
G&C Coplien — Lean Architecture
void TransferMoneyContext::doit(){ sourceAccount()->transferTo();}
void TransferMoneyContext::lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup InvestmentAccount *investmentAccount = new InvestmentAccount; investmentAccount->increaseBalance(Euro(100.00)); // prime it with some money sourceAccount_ = investmentAccount; destinationAccount_ = new SavingsAccount; destinationAccount_->increaseBalance(Euro(500.00)); // start it off with money amount_ = Euro(30.00);}
TransferMoneyContext::MoneySource *TransferMoneyContext::sourceAccount() const { return sourceAccount_;}
TransferMoneyContext::MoneySink *TransferMoneyContext::destinationAccount() const { return destinationAccount_;}
Currency TransferMoneyContext::amount() const{ return amount_;}
Account Example: Transfer Money
G&C Coplien — Lean Architecture
Account Example: PayBills/* * PayBillsContext.h * * Created by James Coplien on 9/13/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#ifndef _PAYBILLSCONTEXT_H#define _PAYBILLSCONTEXT_H
#include "TransferMoneyContext.h"#include "Creditor.h"#include <vector>
class Creditor;
class PayBillsContext: public Context{ template <typename T> auto CREDITORS() { return static_cast<T*>(Context::currentContext_)->creditors(); } template <typename T> auto SOURCE_ACCOUNT() { return static_cast<T*>(Context::currentContext_)->sourceAccount(); }
G&C Coplien — Lean Architecture
Account Example: PayBillspublic: PayBillsContext(); ~PayBillsContext(); TransferMoneyContext::MoneySource *sourceAccount() const; std::vector<Creditor*> creditors() const; // Role behaviours void doit() { // While object contexts are changing, we don't want to // have an open iterator on an external object. Make a // local copy. for (auto& credit : CREDITORS<PayBillsContext>()) { try { // Note that here we invoke another use case TransferMoneyContext transferTheFunds(credit->amountOwed(), SOURCE_ACCOUNT<PayBillsContext>(), credit->account()); transferTheFunds.doit(); } catch (const InsufficientFunds&) { throw; } } }private: void lookupBindings(); TransferMoneyContext::MoneySource *sourceAccount_; std::vector<Creditor *> creditors_;};
#endif
G&C Coplien — Lean Architecture
Account Example: PayBills/* * PayBillsContext.cpp * * Created by James Coplien on 9/17/08. * Copyright 2008 Gertrud & Cope. All rights reserved. */
#include "PayBillsContext.h"#include "InvestmentAccount.h"#include "SavingsAccount.h"#include "Creditor.h"
PayBillsContext::PayBillsContext(): Context() { lookupBindings();}
PayBillsContext::~PayBillsContext() { delete sourceAccount_; for (Creditor *creditor : creditors_) delete creditor;}
G&C Coplien — Lean Architecture
Account Example: PayBillsvoid PayBillsContext::lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup TransferMoneyContext::TransferMoneySource<InvestmentAccount> *investmentAccount = new InvestmentAccount; investmentAccount->increaseBalance(Euro(100.00)); // prime it with some money sourceAccount_ = investmentAccount; creditors_.push_back(new ElectricCompany); creditors_.push_back(new GasCompany);}
TransferMoneyContext::MoneySource *PayBillsContext::sourceAccount() const { return sourceAccount_;}
std::vector<Creditor *> PayBillsContext::creditors() const { return creditors_;}
G&C Coplien — Lean Architecture
Account Example: main//// main.cpp// DCI Money Transfer//// Created by James Coplien on 12/07/16.// Copyright © 2016 Gertrud & Cope. All rights reserved.//
#include <iostream>
#include "TransferMoneyContext.h"#include "PayBillsContext.h"#include <memory>
int main() { auto aNewUseCase = std::unique_ptr<TransferMoneyContext>( new TransferMoneyContext()); aNewUseCase->doit(); auto anotherNewUseCase = std::unique_ptr<PayBillsContext>( new PayBillsContext()); anotherNewUseCase->doit();
return 0;}
G&C Coplien — Lean Architecture
Account Example: Exceptions/* * MyExceptions.h * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#ifndef _MYEXCEPTIONS_H#define _MYEXCEPTIONS_H
#include <exception>
class InsufficientFunds : public std::exception{public:
InsufficientFunds();};
#endif
G&C Coplien — Lean Architecture
Account Example: Exceptions/* * MyExceptionsImplementation.cpp * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#include "MyExceptions.h"
InsufficientFunds::InsufficientFunds(){}
G&C Coplien — Lean Architecture
Account Example: MyTime/* * MyTime.h * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#ifndef _MYTIME_H#define _MYTIME_H
#include <string>
class MyTime {public: MyTime() = default; explicit MyTime(long long); explicit MyTime(const std::string& timeAsString); ~MyTime(); MyTime(const MyTime& t); MyTime& operator=(const MyTime &t);
friend bool operator<(const MyTime &x, const MyTime &y);};
#endif
G&C Coplien — Lean Architecture
Account Example: MyTime/* * MyTimeImplementation.cpp * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#include "MyTime.h"
MyTime::MyTime(long long) { }
MyTime::MyTime(const std::string& timeAsString) { }
MyTime::~MyTime() { }
MyTime::MyTime(const MyTime &t) { }
MyTime &MyTime::operator=(const MyTime &t){ return *this; }
bool operator<(const MyTime &x, const MyTime &y){ return true; }
G&C Coplien — Lean Architecture
Account Example: Globals/* * Globals.h * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#ifndef _GLOBALS_H#define _GLOBALS_H
#include "MyTime.h"
extern void endTransaction();extern void beginTransaction();extern MyTime DateTime();
#endif
G&C Coplien — Lean Architecture
Account Example: Globals/* * GlobalsImplementation.cpp * * Created by James Coplien on 9/2/08. Updated to C++14 7/12/16. * Copyright ©2016 Gertrud & Cope. All rights reserved. */
#include "Globals.h"#include "MyTime.h"#include <iostream>
void endTransaction() { std::cout << "::endTransaction()" << std::endl;}
void beginTransaction() { std::cout << "::beginTransaction()" << std::endl;}
MyTime DateTime() { return MyTime(0);}
G&C Coplien — Lean Architecture
Account Example: MoneyPort//// MoneyPort.h// DCI Money Transfer//// Created by James Coplien on 12/07/16.// Copyright © 2016 Gertrud & Cope. All rights reserved.//
#ifndef MoneyPort_h#define MoneyPort_h
#include "Context.h"
class MoneyPort { // Common private utility functions
template <typename T, typename U> auto SELF(U* u) { return static_cast<T*>(u); }
template <typename T> auto RECIPIENT() { auto result = dynamic_cast<T*>(Context::currentContext_); if (result) return result->destinationAccount(); throw std::bad_cast(); // ("dynamic cast failed"); }