The scenario
Imagine the following fictitious method:
public Account Example() { var account = new Account(); account.State = UsaState.California; account.Open(); PeopleManager.SetAccountPeople(account, 0); MemberLevelServiceHelper.SetAccountHolderLevel(account, MemberLevel.Gold); new DebtCalculator().CalculateDebt(account); account.Complete(); Reports.GetCompletedAccountReport().AddAccount(account); AccountUtil.Close(account); NoteAddingSingleton.Instance.AddNote(account, "I'm an example!"); return account; }It looks like it creates an account, does some setup, and returns it. It's calling a bunch of things from all over the place. I know from experience that I'd have a hard time remembering where everything is and what order to call it in. Some things are on the account, some are singletons, some are helpers and utils - exactly the kind of situation that anyone who's joined a long term project with many other developers is familiar with. If only there was a unified interface for all these subsystems - oh wait - isn't that the definition of the classic Facade pattern?
The patterns
class AccountLifecycleFacade { public Account RealAccount { get; set; } public AccountLifecycleFacade() { RealAccount = new Account(); } public void SetState(UsaState state) { RealAccount.State = state; } public void Open() { RealAccount.Open(); } public void SetAccountPeople(int count) { PeopleManager.SetAccountPeople(RealAccount, count); } public void MakeGoldLevel() { MemberLevelServiceHelper.SetAccountHolderLevel(RealAccount, MemberLevel.Gold); } /* ... etc ... */ }Having this facade lets us work through the lifecycle of an account without worrying about where that functionality is actually implemented; no more trying to remember which helper, manager, singleton, service, utility, or business class to use since we only need to look at one place. With one class for the hi-level functionality, we have a clear api and intellisense will show us what we can do with an account.
public Account FacadeExample() { var account = new AccountLifecycleFacade(); account.SetState(UsaState.California); account.Open(); account.SetAccountPeople(0); account.MakeGoldLevel(); account.CalculateDebt(); account.Complete(); account.AddToCompletedAccountReport(); account.Close(); account.AddNote("I'm an example!"); return account.RealAccount; }And since it works with a single account at a time we can make it a wrapper. If we make the wrapper return itself at the end of each method then we'll be able to call it using a fluent interface.
class AccountLifecycleAdapter { public Account RealAccount { get; set; } public AccountLifecycleAdapter(Account account) { RealAccount = account; } public AccountLifecycleAdapter SetState(UsaState state) { RealAccount.State = state; return this; } public AccountLifecycleAdapter Open() { RealAccount.Open(); return this; } /* ... etc ... */ }And now our example method is a little simpler.
public Account Example() { var account = new AccountLifecycleAdapter(new Account()) .SetState(UsaState.California) .Open() .SetAccountPeople(0) .MakeGoldLevel() .CalculateDebt() .Complete() .AddToCompletedAccountReport() .Close() .AddNote("I'm an example!"); return account.RealAccount; }That's much easier for me to read, understand, and test. I'm sure this facade/adapter has been done a hundred times before. I see one potential problem though: it looks like some of the methods are only valid at certain times. I'd wager that you can't complete a non-open account and that you really shouldn't add an account to the Closed Account Report until it's closed. We could rely on runtime assertions to verify the preconditions and post conditions of our new Account api, which is a fine idea, but wouldn't it be nice if we could catch this at compile time?
The magic
If the preconditions and postconditions of these methods were expressed in the type system then not only would the compiler stop us from running invalid code, but intellisence would only show the methods that are valid for the account we have. In order for that to work we need to change the type of our AccountLifecycleAdapter to expose the state of the account. For this example, certain things may only be valid when the account is new, opened, completed, or closed and certain things may only be valid for new, bronze, silver, or gold members. Not only do these methods have preconditions based on the status and member level of the account, but they also have postconditions that change the state of the account.
Method | Precondition | Postcondition |
---|---|---|
Open() | Status = New | Status = Open |
Complete() | Status = Open | Status = Complete |
Close() | Status = Open or Complete | Status = Close |
AddToCompletedAccountReport() | Status = Complete | Status is unchanged |
Although we can change the state of an object, we can't change its type. Luckily we don't need to since we are returning an AccountLifecycleAdapter with each method: we can return a different type that represents the state of the account and only supports the methods that are valid for that account state.
One possible way is to have a new class for each possible state.
class AccountLifecycleAdapter_NewStatus_NoLevel { public Account RealAccount { get; set; } public AccountLifecycleAdapter_NewStatus_NoLevel(Account account) { RealAccount = account; } public AccountLifecycleAdapter_NewStatus_NoLevel SetState(UsaState state) { RealAccount.State = state; return this; } public AccountLifecycleAdapter_OpenStatus_NoLevel Open() { RealAccount.Open(); return new AccountLifecycleAdapter_OpenStatus_NoLevel(RealAccount); } /* ... etc ... */ }This would make sure that the wrapper expresses the state of the account and that only valid methods are called for that account. But it could become a cartesian explosion; this example as 4 statuses and 4 member levels for a total of 16 classes you need to make. Since some things are applicable for several states (like CalculateDebt and Close from our earlier table), they'd need to be implemented in several different wrapper classes. Messy. Fortunately for C# users, the language offers a convenient way out by way of generics and extension methods. Instead of having the class name express the state, we can use a generic type parameter for each run time variable we wish to track; status and member level in this case. A method that creates a copy with the type parameters we want would let us chain the wrapped object along while changing the type parameters.
class AccountLifecycleAdapter<Status, Level> { public Account RealAccount { get; set; } public AccountLifecycleAdapter(Account account) { RealAccount = account; } public AccountLifecycleAdapter<NewStatus, NewLevel> SetTypes<NewStatus, NewLevel>() { return new AccountLifecycleAdapter<NewStatus, NewLevel>(RealAccount); } }
And a non-generic version will make sure we create the adapter with the correct default type parameters.
public class AccountLifecycleAdapter { public static AccountLifecycleAdapter<New, NoLevel> New(Account account) { return new AccountLifecycleAdapter<New, NoLevel>(account); } }
The methods that used to belong to the wrapper can now be moved into extension methods.
public static class AccountLifecycleAdapterExtensions { public static AccountLifecycleAdapter<New,NoLevel> SetState(this AccountLifecycleAdapter<New,NoLevel> adapter, UsaState state) { adapter.RealAccount.State = state; return adapter.SetTypes<New,NoLevel>(); } public static AccountLifecycleAdapter<Open,L> Open<L>(this AccountLifecycleAdapter<New,L> adapter) where L : AccountLevel { adapter.RealAccount.Open(); return adapter.SetTypes<Open,L>(); } public static AccountLifecycleAdapter<Open,L> SetAccountPeople<L>(this AccountLifecycleAdapter<Open,L> adapter, int count) where L : AccountLevel { PeopleManager.SetAccountPeople(adapter.RealAccount, count); return adapter; } /* ... etc ... */If we look at our extension methods then we see something I think is very interesting. With this kind of typefull api, the input parameter type represents the precondition and the return type represents the post condition.
public static AccountLifecycleAdapter<Open,L> Open<L>(this AccountLifecycleAdapter<New,L> adapter) where L : AccountLevel
Method | Precondition | Postcondition |
---|---|---|
Open | Status = New | Status = Open |
Since the valid status is expressed as a precondition and the what the method changes is expressed as the return type, the compiler and intelisense can make sure you only chain methods that make sense. If a method returns an AccountLifecycleAdapter<Open, Silver> then you can't call an extension method that expects a Closed account. Intellesence won't suggest invalid methods and the compiler won't allow them. As an added bonus, changing the preconditions or post conditions in a way that breaks code will prevent a compile instead of lying dormant until discovered, investegated, and fixed during manual or automated testing.
All we need now are the types that represent the runtime state.
The Phantom Types
These types are a little odd in that they are only used for compile time info and we never create instances of them. The Haskell community would call them Phantom Types so we should too.
namespace PhantomTypes { public abstract class PhantomType {} public abstract class AccountStatus : PhantomType {} public abstract class New : AccountStatus {} public abstract class Open : AccountStatus {} public abstract class Completed : AccountStatus {} public abstract class Closed : AccountStatus {} public abstract class AccountLevel : PhantomType {} public abstract class NoLevel : AccountLevel {} public abstract class Bronze : AccountLevel {} public abstract class Silver : AccountLevel {} public abstract class Gold : AccountLevel {} }I like to make it double obvious that these are different than normal types with a separate PhantomType namespace and PhantomType base class.
Since the type parameters are part of the method signature, you can have different overloaded methods for different states. Here we can see that an overdraft will close the account unless it's a gold level account; they just get a warning.
public AccountLifecycleAdapter<S,GoldLevel> Overdraft<L>(this AccountLifecycleAdapter<S,GoldLevel> adapter) where S : AccountStatus { NotificationService.GetInstance().NotifyOfOverdraft(adapter.Account); return adapter.SetTypes<S,GoldLevel>() } public AccountLifecycleAdapter<Closed,L> Overdraft<S,L>(this AccountLifecycleAdapter<S,L> adapter) where S : AccountStatus where L : AccountLevel { AccountUtil.CloseDueToOverdraft(adapter); return adapter.SetTypes<Closed,L>() }Interfaces can be used if an extension method will work with multiple different phantom types.
public abstract class AccountStatus : PhantomType {} public interface NotNew { } public abstract class New : AccountStatus {} public abstract class Open : AccountStatus, NotNew {} public abstract class Complete : AccountStatus, NotNew {} public abstract class Closed : AccountStatus, NotNew {}And the method that uses it would add that as a type parameter constraint.
public static AccountLifecycleAdapter<S,L> AddNote<S,L>(this AccountLifecycleAdapter<S,L> adapter, string note) where S : AccountStatus, NotNew where L : AccountLevel { NoteAddingSingleton.Instance.AddNote(adapter.RealAccount, note); return adapter; }
Where to go from here
A lot more functionality and safety can be added. One idea is to add a check to the adapter's constructor or SetTypes method that asserts the phantom types match the actual run time state. This would let you know if your expectations of what's going on are correct or not.
public void CheckTypes<NewStatus, NewLevel>() { if (typeof(GoldLevel).IsAssignableFrom(typeof(NewLevel)) && RealAccount.MemberLevel != MemberLevel.Gold) throw new InvalidOperationException("expected account member level Gold; got " + RealAccount.MemberLevel); }
Another thing the adapter could do is collect all the ancillary objects that get created along the way. That way, once you're done with it you not only have your account, or whatever you were making, but any other related objects and you won't need to query for them.
One idea I haven't tried (yet...) is generating documentation about the system based on the adapter's extension methods. If you think about it, each of these methods is a transition from preconditions to postconditions. Each precondition and postcondition would be a state. You could use reflection, or old fashioned string manipulation, to capture all of the states and transitions within an adapter and create a state transition diagram. In a complex system this could be very useful for explaining the system to new hires, managers, users, other developers, or testers. Speaking of testing, knowing all of the different states transitions would also help ensure you are adequately testing all code paths.
I'm not entirely sure what to call this construct or "pattern". It's sort of the opposite of the State pattern; instead of an object changing behavior at run time, it's exposing a different api at design time. It's sort of like a Builder since you're building an object through a path that's more complex than a simple constructor. It can be a Facade and call other sub systems, but it doesn't have to be. It's also an Adapter since it exposes functionality related to the wrapped object. In my mind, the important thing is that it this is an adapter that only exposes methods that are valid for the run time state of the wrapped object. For now I'm calling this a Typefull Dynamic API but there may be a better name.
The Summary
Using the facade pattern can greatly aid in readability, understandability, and testability of a complex system with many subsystems - that's been known by many people for many years now. Using an object that returns itself in each method makes fluent interfaces possible - that's been known for a few years too. And using the signature of the method to express the preconditions and postconditions and making sure things are only called on object with the correct state has been around for decades, although I think the object oriented crowd has missed out on a lot of this since you can't change the type of an object when its state is changed. Extension methods give C# developers a chance to combine these in a useful way to get something new: a Typefull Dynamic API that changes its public api at design time based on what would be the state of another object at runtime.
Why not just use state machine to define such kind of business logic?
ReplyDeleteThis way allows the compiler to verify that the transition from one state to the other is valid while retaining the traditional object oriented dot notation. In other words; if you view each static method as a transition and the input and output as the beginning and ending state, then it is a type-level state machine. The "type-level" part is what makes this different than a common state machine. Unless you know of another way of having these things checked by the compiler? I'd be interested in other approaches.
ReplyDeleteGreat Article
ReplyDeleteC# Training
C# Training
C# OOP Interview Questions
C# Online Training
C-Sharp Training
Dot Net Training in Chennai
.Net Online Training
Dot Net Interview Questions
Really interesting content which is unique which provided me the required information.
ReplyDeleteDot Net Training in Chennai | .Net training in Chennai | FITA Training | FITA Velachery .
Needed to compose you a very little word to thank you yet again regarding the nice suggestions you’ve contributed here.
ReplyDeleteHadoop Training in chennai
This is excellent information. It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me..
ReplyDeleteEmbedded System training in Chennai | PLC training institute in Chennai
I feel really happy to have seen your webpage and look forward to so many more entertaining times reading here. Thanks once more for all the details.
ReplyDeletepython training in chennai
python training in bangalore
python online training
python training in pune
Good piece of work ,enjoyed reading it. The precision given here is good. Hoping a lot from you. Thankyou for sharing.
ReplyDeletejava training in chennai
java training in bangalore
java online training
java training in pune
After reading your post I understood that last week was with full of surprises and happiness for you. Congratz! Even though the website is work related, you can update small events in your life and share your happiness with us too.
ReplyDeleteData Science training in btm
Data Science training in rajaji nagar
Data Science training in chennai
Data Science training in kalyan nagar
Data Science training in electronic city
Data Science training in USA
selenium training in chennai
selenium training in bangalore
Nice post. By reading your blog, i get inspired and this provides some useful information. Thank you for posting this exclusive post for our vision.
ReplyDeleterpa training in Chennai
rpa training in Chennai
rpa training in Chennai
rpa training in velachery
rpa training in tambaram
rpa training in sholinganallur
rpa training in anna nagar
rpa online training
After seeing your article I want to say that the presentation is very good and also a well-written article with some very good information which is very useful for the readers....thanks for sharing it and do share more posts like this.
ReplyDeletepython training in rajajinagar
Python training in btm
Python training in usa
I have been meaning to write something like this on my website and you have given me an idea. Cheers.
ReplyDeleteangularjs Training in chennai
angularjs Training in chennai
angularjs-Training in tambaram
angularjs-Training in sholinganallur
angularjs-Training in velachery
Really very nice blog information for this one and more technical skills are improve,i like that kind of post.
ReplyDeleteBlueprism training in Chennai
Blueprism training in Bangalore
Your very own commitment to getting the message throughout came to be rather powerful and have consistently enabled employees just like me to arrive at their desired goals.
ReplyDeletesafety training in chennai
Thanks for the information shared with us. aws online training
ReplyDeleteI am satisfied that you simply shared this useful information with us.
ReplyDeletePlease stay us informed like this. Thanks for sharing.
Java Training in Chennai
Python Training in Chennai
IOT Training in Chennai
Selenium Training in Chennai
Data Science Training in Chennai
FSD Training in Chennai
MEAN Stack Training in Chennai
Very nice post here thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
ReplyDeleteMachine learning training in chennai
machine learning course fees in chennai
top institutes for machine learning in chennai
Android training in chennai
PMP training in chennai
I love the blog. Great post. It is very true, people must learn how to learn before they can learn. lol i know it sounds funny but its very true. . .
ReplyDeleteangularjs online training
apache spark online training
informatica mdm online training
devops online training
aws online training
Thanks for sharing this information, it helps me to learn new things. Continue sharing more like this.
ReplyDeleteR Training Institute in Chennai
Thanks for your sharing
ReplyDeleteVMware Training in Chennai
DevOps Training in Chennai
Attend The Python training in bangalore From ExcelR. Practical Python training in bangalore Sessions With Assured Placement Support From Experienced Faculty. ExcelR Offers The Python training in bangalore.
ReplyDeletepython training in bangalore
ReplyDeleteI feel very grateful that I read this. It is very helpful and very informative and I Python classes in pune really learned a lot from it.
I learned World's Trending Technology from certified experts for free of cost. I Got a job in decent Top MNC Company with handsome 14 LPA salary, I have learned the World's Trending Technology from Python training in pune experts who know advanced concepts which can help to solve any type of Real-time issues in the field of Python. Really worth trying instant approval blog commenting sites
ReplyDelete"Just saying thanks will not just be sufficient, for the fantastic lucidity in your writing. I will instantly grab your articles to get deeper into the topic. And as the same way ExcelR also helps organisations by providing data science courses based on practical knowledge and theoretical concepts. It offers the best value in training services combined with the support of our creative staff to provide meaningful solution that suits your learning needs.
ReplyDeleteData Science Training in Pune "
very nice..
ReplyDeletegibraltar web hosting
iceland web hosting
lebanon web hosting
lithuania shared web hosting
inplant training in chennai
good ....
ReplyDeleteluxembourg web hosting
mauritius web hosting mongolia web hosting
namibia web hosting
norway web hosting
rwanda web hosting
spain hosting
turkey web hosting
venezuela hosting
vietnam shared web hosting
nice blogs....
ReplyDeleteinternship in chennai for ece students
internships in chennai for cse students 2019
Inplant training in chennai
internship for eee students
free internship in chennai
eee internship in chennai
internship for ece students in chennai
inplant training in bangalore for cse
inplant training in bangalore
ccna training in chennai
Very Nice...
ReplyDeleteinternship in chennai for ece students with stipend
internship for mechanical engineering students in chennai
inplant training in chennai
free internship in pune for computer engineering students
internship in chennai for mca
iot internships
internships for cse students in
implant training in chennai
internship for aeronautical engineering students in bangalore
inplant training certificate
hiii nyc..
ReplyDeleteinternships for cse students in bangalore
internship for cse students
industrial training for diploma eee students
internship in chennai for it students
kaashiv infotech in chennai
internship in trichy for ece
inplant training for ece
inplant training in coimbatore for ece
industrial training certificate format for electrical engineering students
internship certificate for mechanical engineering students
very nice post and useful information........
ReplyDeleter programming training in chennai
internship in bangalore for ece students
inplant training for mechanical engineering students
summer internships in hyderabad for cse students 2019
final year project ideas for information technology
bba internship certificate
internship in bangalore for ece
internship for cse students in hyderabad
summer training for ece students after second year
robotics courses in chennai
useful information..nice..
ReplyDeletedevops-engineer-resume-samples
digital-marketing-resume-samples
digital-marketing-resume-samples
electronics-engineer-resume-sample
engineering-lab-technician-resume-samples
english-teacher-cv-sample
english-teacher-resume-example
english-teacher-resume-sample
excel-expert-resume-sample
executive-secretary-resume-samples
useful information..nice..
ReplyDeletedevops-engineer-resume-samples
digital-marketing-resume-samples
digital-marketing-resume-samples
electronics-engineer-resume-sample
engineering-lab-technician-resume-samples
english-teacher-cv-sample
english-teacher-resume-example
english-teacher-resume-sample
excel-expert-resume-sample
executive-secretary-resume-samples
Great Blog. Thnaks.
ReplyDeleteSAP Training in Chennai
Java Training in Chennai
Software Testing Training in Chennai
.Net Training in Chennai
Hardware and Networking Training in Chennai
AWS Training in Chennai
Azure Training in Chennai
Selenium Training in Chennai
QTP Training in Chennai
Android Training in Chennai
ReplyDeleteNice article and thanks for sharing with us. Its very informative
Machine Learning Training in Hyderabad
Extraordinary Blog. Provides necessary information.
ReplyDeletebest digital marketing course in chennai
best digital marketing training in chennai
Great post. keep sharing such a worthy information.
ReplyDeletePHP Training in Chennai
PHP Training
PHP Training in Bangalore
Great post. keep sharing such a worthy information.
ReplyDeleteAngularjs Training in Chennai
Angularjs Certification Online
Angularjs Training In Bangalore
This post is so interactive and informative.keep update more information...
ReplyDeleteDigital Marketing Course in Tambaram
Digital Marketing Course in Anna Nagar
Great post. keep sharing such a worthy information.
ReplyDeleteData Science Training in Chennai
yurtdışı kargo
ReplyDeleteresimli magnet
instagram takipçi satın al
yurtdışı kargo
sms onay
dijital kartvizit
dijital kartvizit
https://nobetci-eczane.org/
N73U
Portekiz yurtdışı kargo
ReplyDeleteRomanya yurtdışı kargo
Slovakya yurtdışı kargo
Slovenya yurtdışı kargo
İngiltere yurtdışı kargo
P1GTLİ
Angila yurtdışı kargo
ReplyDeleteAndora yurtdışı kargo
Arnavutluk yurtdışı kargo
Arjantin yurtdışı kargo
Antigua ve Barbuda yurtdışı kargo
1EUEVX
Nice post.Thanks for sharing it with us.
ReplyDeleteData science training in Nagpur
Thanks for sharing post with us . Keep sharing
ReplyDeleteData Science Course in Pune
Learn many things from your blog, great work, keep shining and if you are intresting in data engineering then checkout my blog data science course in satara
ReplyDelete