1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | package examples; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.*; import java.util.stream.Collectors; public class DoThisInsteadOfJavaBeans { static class Transaction{ //ONLY USING IMMUTABLES final public int id; final public int amount; final public long date; final public String desc; public Transaction( int id, int amount, long date, String desc) { this .id = id; this .amount = amount; this .date = date; this .desc = desc; } public Transaction( int id, int amount, Date date, String desc) { this (id, amount, date.getTime(), desc); } public Date getDate() { return new Date(date); } @Override public String toString() { return "Transaction{" + "id=" + id + ", amount=" + amount + ", date=" + getDate() + ", desc='" + desc + '\ '' + '}' ; } } static class Account{ //ONLY IMMUTABLES final public int accountId; final public ImmutableList<Transaction> transactions; public Account( int accountId){ this .accountId = accountId; this .transactions = ImmutableList.of(); } public Account( int accountId, List<Transaction> tList){ this .accountId = accountId; transactions = ImmutableList.copyOf(tList); } public Account withTransaction(Transaction t){ return new Account(accountId, ImmutableList.<Transaction>builder().addAll(transactions).add(t).build()); } public Account withTransactionReplaced(Transaction t){ return new Account(accountId, ImmutableList.<Transaction>builder() .addAll(transactions.stream().filter(x -> x.id != t.id).collect(Collectors.toList())) .add(t) .build()); } @Override public String toString() { return "Account{" + "accountId=" + accountId + ", transactions=" + transactions + '}' ; } } interface AccountChangedListener { void accountEdited(Account a); } static class AccountEditor{ volatile private Account a; final private ImmutableList<AccountChangedListener> listeners; public AccountEditor(Account a, List<AccountChangedListener> listeners){ this .a = a; this .listeners = ImmutableList.copyOf(listeners); } public void addTransaction(Transaction t){ a = a.withTransaction(t); listeners.forEach(l -> l.accountEdited(a)); } public void updateTransaction(Transaction t){ a = a.withTransactionReplaced(t); listeners.forEach(l -> l.accountEdited(a)); } } static class PNLReport implements AccountChangedListener{ //WE CREATED THIS CLASS SO THAT WE CAN SWAP OUT BOTH ACCOUNTS AND TOTAL IN ONE GO WITHOUT SYNCHRONIZATION static class PNLState{ public ImmutableMap<Integer, Account> accounts; public Integer total; public PNLState(ImmutableMap<Integer, Account> accounts) { this .accounts = ImmutableMap.copyOf(accounts); this .total = computeTotals(accounts); } private int computeTotals(ImmutableMap<Integer, Account> accounts){ final int [] totalFunds = new int [] { 0 }; accounts.values() .forEach(v -> v.transactions .forEach(t -> totalFunds[ 0 ] += t.amount)); return totalFunds[ 0 ]; } } volatile private PNLState pnlState; public PNLReport(Map<Integer, Account> accounts){ this .pnlState = new PNLState(ImmutableMap.copyOf(accounts)); } @Override public void accountEdited(Account a) { Map<Integer, Account> m = new HashMap(pnlState.accounts); m.put(a.accountId, a); this .pnlState = new PNLState(ImmutableMap.copyOf(m)); } public void print(){ System.out.println( "Accounts:" + pnlState.accounts); System.out.println( "Total:" + pnlState.total); } } static class BalanceSheet implements AccountChangedListener{ public BalanceSheet(ImmutableMap<Integer, Account> accounts){} //Another screen, similar implementation to PNLReport @Override public void accountEdited(Account a) { //.. } } public static void main(String[] argc){ Account a = new Account( 1 ); Transaction t = new Transaction( 1 , 100 , new Date(), "First Transaction" ); Account accountWithTransaction = a.withTransaction(t); ImmutableMap<Integer, Account> chartOfAccounts = ImmutableMap.of(accountWithTransaction.accountId, accountWithTransaction); PNLReport pnl = new PNLReport(chartOfAccounts); BalanceSheet balances = new BalanceSheet(chartOfAccounts); AccountEditor editor = new AccountEditor(accountWithTransaction, ImmutableList.of(pnl, balances)); pnl.print(); editor.addTransaction( new Transaction( 2 , 200 , new Date(), "Second Transaction" )); //ADD WORKED pnl.print(); editor.updateTransaction( new Transaction( 1 , 50 , new Date(), "Modified First Transaction" )); //UPDATE WORKED pnl.print(); //EVERYTHING IS AUTOMATICALLY THREAD-SAFE WITHOUT ANY SYNCHRONIZATION new Thread( new Runnable() { @Override public void run() { pnl.print(); } }).start(); //NOT POSSIBLE TO CORRUPT THE STATE ACCIDENTALLY //t.setAmount(1000); } } |