From: Stefan F. <ste...@we...> - 2012-07-27 05:05:25
|
All, Another part of the Rails2.0 series, introducing the most important part of the state package, the Portfolio class(es). This is a pretty long text, but it should serve as documentation for future usage. As usual I will state the goal of the design first, followed by more details about the implementation. Critical comments most welcome. Stefan A) The idea behind Portfolio The main problem of the previous undo-mechanism was the ownership of the "Moveable" items: This was not (fully) automated and the side-effects had to be taken care manually. The "Moveable" interface of Rails1.x is renamed to "Ownable" in Rails2.0, but both indicate items that can change their owner during the game (e.g. certificates, trains etc.). In Rails2.0 all ownable Items have to be stored in a "Portfolio" which itself is a subclass of State (thus it has built-in undo/redo and observable mechanisms). A portfolio is a generic collection, thus has a type which indicates what can be stored: e.g. Portfolio<Train>. Each portfolio has an "Owner", which is another interface of the state package to be implemented by those classes that can own items (Rails 1.x: MoveableHolder). Moving an item from one portfolio to the other is very simple: One calls the method item.moveTo(Owner newOwner) and behind the scenes the following happens: A) The item is added to the portfolio of the newOwner B) The item is removed from the portfolio of the oldOwner C) The owner of the item changes from the oldOwner to the newOwner This is similar to what (most of the time) happened in Rails1.x, however there is no manual coding involved anymore in Rails2.0 B) More details for the usage of Portfolio In fact there are two subclasses of Portfolio, which itself is an abstract class: PortfolioSet and PortfolioMap *** How to create Portfolios? It is important to know that each Owner can have only one Portfolio for each Item-Type. However this is the only restriction that exist. Other than that Rails does not care if the Portfolio is created inside the Owner (as a variable there) or somewhere else (e.g. inside a Model). However in the latter case of an indirect holding one has to implement the interface PortfolioHolder and the parent of the PortfolioHolder has to be an Owner. Then item.moveTo(owner) will move the item into the Portfolio of the PortfolioHolder that belongs to the owner. An example: In Rails1.x most items where stored inside a class called "Portfolio". This still exist however it is called "PortfolioModel" now (as it is a subclass of Model). PortfolioModel itself implements PortfolioHolder, thus it is able to hold Portfolios instead of the (true) owner. public class Player() implements Owner { private final PortfolioModel portfolios = PortolioModel.create(this, "Portfolios"); } public class PortfolioModel() implements PortfolioHolder { private final Portfolio<Train> trains = PortfolioSet.create(this, "Trains"); } If one wants to move the train to a new player, train.moveTo(newPlayer) is sufficient. Remark: In the example above the item hierachy automatically allows to locate the portfolio with root.locate("/Companies/PRR/Portfolios/Trains") returns the train portfolio of the company PRR. *** How to create an Ownable Class? Important rule first: Better do not implement the Ownable Interface, but extend from the OwnableItem abstract class (unless you really no what you do and it is really necessary to no extend the abstract class). The major restriction is that an Ownable type can only be stored in one specific typed portfolio. Take an example again: PublicCertificate is a subclass/implementation of Certificate. So in principle it is possible to store PublicCertificate(s) either in Portfolio<Certificate> or Portfolio<PublicCertificate>. To allows Rails identify the correct portfolio to use (and for some other technical reasons with respect to Java Generics) it is required to specify which ones should be used (both variants are possible). Which is used depends which class extends from the OwnableItem: If Certificate extends OwnableItem<Certicate> all certificate(s) and all objects of its subclasses can only be stored inside portfolios of the type Portfolio<Certificate>. If PublicCerticate extends OwnableItem<PublicCerticate>, all public certificates are stored inside portfolio of the type Portfolio<PublicCertificate>. However this implies that Certificate can only be an Interface, as Java one allows extending one class (a case where the concept of Mixins would help). Other than that it is easy. Assume we decide that PublicCerticate is the correct level to extend OwnableItem. Except extending one only has to call the super-constructor. public class PublicCertificate extends OwnableItem<PublicCertificate> { public PublicCertificate(Owner parent, String id) { super(parent, id, PublicCertificate.class) } } The constructor of OwnableItem requires the class of the derived class as an argument to be able to locate the correct portfolio. *** PortfolioSet or PortfolioMap? The default implementation of Portfolio is a PortfolioSet: Here all items are thrown into one bucket, each ownable item is unique (has its own id), thus it is implemented as a set. General Remark: If items are not-unique, there are other structures to deal with those (non-unique items implement Countable and get stored in a Wallet). A more sophisticated variant is PortfolioMap. Here the portfolio is structured by a "type" attribute of the items. An example is the storage of PublicCertificate(s). All certificates for all companies get stored in one PortfolioMap (per owner). This allows to call portfolio.get(company) to retrieve only those certificates of the specified company. To be able to retrieve all certficates for one company only, the PortfolioMap keeps track of the type. Requirement is that the item implements the "Typable" interface. An example makes things clear: This definition of PublicCerticate: public class PublicCertificate extends OwnableItem<PublicCertificate> implements Typable<PublicCompany> { PublicCompany getType(); } allows to store those inside PortfolioMap<PublicCompany, PublicCertificate> portfolio = PortfolioMap.create(this, "Shares"); Moving a share into the map still requires only: share.moveTo(newOwner). The portfolio knows automatically where to put the share according to its method getType(). Further Remark: PortfolioSet and PortfolioMap work interchangeable, thus even in the case above it is still possible to store PublicCertificates inside a PortfolioSet<PublicCertificate>. Think the example of a company which is able to buy-back its own share, here a PortfolioSet is fully sufficient and can be used in parallel to the PortfolioMap(s) of the players. C) Are the restrictions above severe? *** Only one portfolio of each type per owner? Seems pretty bad at the beginning, but there are easy workarounds. * Example: Bank has several portfolios (ipo, pool, unavailable, scrapheap). Solution: Add a new structure BankPortfolio which implements Owner itself. Inside Bank create BankPortfolio ipo = new BankPortfolio.create(this, "ipo"); Then item.moveTo(Bank.ipo) works correctly. * Another example: Used and Unused Tokens of a company One could think that there are two portfolios required (one for used, one for unused). Solution: Make Unused Tokens the portfolio of the company. For usage they get moved to a new owner anyway. To keep track of the used tokens it is still possible to use a HashSetState<Token> in parallel. * A third example: BaseTokens and BonusTokens If both of them have to be stored inside a Portfolio<Token> (because Token extends OwnableItem<Token>, there is no way to separate them. Solutions: a) Add two HashSetState<BaseToken> and HashSetState<BonusToken> in parallel. b) Put the portfolio inside a model and provide .getBonusTokens() and .getBaseTokens() methods. c) Make Tokens implement Typable<Class<? extends Token>> and return the class as type. Then Tokens can be stored inside PortfolioMap<Class<? extends Token>, Token> and portfolio.get(BaseToken.class) and portfolio.get(BonusToken.class) works. D) Implementation Details The implementation is (surprisingly even to me) short. The main class is the PortfolioManager, which is accessible from the StateManager: Internally it stores references to all portfolios created using a (type, owner) tuple as key inside a HashMapState. The OwnableItem itself only has one GenericState<Owner> variable, which tracks the current owner. If item.moveTo(newOwner) is called, PortfolioManager returns both the portfolio of the new and the current owner. This allows to create a PortfolioChange with that information, which is added to the ChangeStack. That's it. |