Separating UI and logic in C#
Does anyone have any advice on keeping logic out of my GUI classes? I try to use good class design and keep as much separated as possible, but my Form classes usually ends up with more non-UI stuff mixed in than I'd like, and it tends to make maintenance a real pain.
(Visual Studio 2008 Professional, C#, Windo开发者_运维百科ws apps).
Many thanks.
Put your logic in a separate assembly; and, build that assembly without its referencing any GUI packages (e.g. System.Drawing
, System.Windows.Forms
, etc.).
It's really just a matter of practice and self discipline. I mean, we've all done it. And we all continue to do it from time to time under the wrong conditions (manager/customer yelling to get something done "right now" vs. "right", etc.).
One thing I do when writing code to drive the UI (more on the web side, but the same thing applies) is to ask myself with each unit of code (a single line, a conditional, a loop, etc.) whether that piece of code depends on the presence of the UI. If I'm writing to a text box, that's UI-dependent, so it goes there. But if I'm calculating the result that will go in that text box, that's probably business logic.
Another approach (as ChrisW alluded to out while I'm typing) is to develop the logic first in a non-UI class library. Put as much logic in there as you can (use your judgment as to what defines "logic" though) that doesn't depend on UI-based libraries. Then build the UI to make use of that logic. There are different approaches to allow the concurrent development of these two pieces, such as stubbing out the logic assembly behind interface classes and just coding the UI pieces to those interfaces (then using dependency injection to plug the assembly classes into the interfaces), etc.
The 3 layer architecture is what you are looking for.
You build 2 reusable layers:
- Data Access Layer (DAL) which contains only the code needed to read/write from the database
- Business Logic Layer (BLL) which consumes the DAL, contains business rules, validation, and provides a facade for the UI to use
Then on your UI project your reference the reusable layers and handle only the UI specific stuff. The UI project talks only to the BLL, with no direct connection to the DAL:
UI <---> BLL <---> DAL
You can have multiple UI layers that consume your reusable components, as well as multiple interchangable DALs if you want to support multiple database types.
You need to look into design patterns like:
Model-View-Controller (MVC) often used by web sites (ASP.NET)
Model-View-View Model (MVVM) often used by WPF
By keeping to one of these you should be able to keep the various parts of your application separate.
There are other patterns that do a similar job.
Also, developing with WPF can help as the UI is defined by the XAML and the code that does the work is the C#. This can provide a basic degree of separation. If you find yourself writing C# code that just manipulates the UI you can take a step back and think "should I do this in XAML?". Obviously there might be things you have to do in the code behind, but it's a start.
Learn how to write controller classes that can be databound to the form and how to perform the databinding. In WinForms this mainly comes down to INotifyPropertyChanged and IDataErrorInfo interfaces on the controller class and using BindingSource instances on the form class.
Then you can write a controller class that contains ALL the data and logic for the UI and the UI class simply binds to it. Your UI class then becomes very thin, and your UI logic (held in the controller) becomes very testable (unit tests are tricky when running against UI classes, but much easier when running against controller classes).
This is the basis of all the MVC/MVVM designs.
Herbie
Typically in situations like this; I create a helper method class that does all the heavy lifting.
As to keeping the logic separate; identify what are the key components, and refactor those out into that helper method class. For example; if I'm processing a GridView to update records based on whether or not they're selected; and if they are, update the ShipDate, in the form; i'd figure out first if the row is selected; then extract the Id field, then the ShipDate, and then pass the Id and ShipDate into a method on my helper class that does all the work.
Unit tests can be your friend here; basically, if you have any code that does "logic" type stuff; it should have a unit test. If it's in the GUI classes; it's difficult to test; however once you refactor it out; the unit test should be trivial.
You should look at the following patterns:
MVC (Model-View-Controller) MVVM (Model-View-View-Model) - Mostly used in WPF with it's rich databinding support. MVP (Model-View-Presenter) - Often used for WinForms and web apps (because of the stateless view)
Check out this blog post which gives an example of how to use MVP to power both a web and WinForms view with one presenter: http://www.cerquit.com/blogs/post/MVP-Part-I-e28093-Building-it-from-Scratch.aspx
Also, a further blog post here describes using the MVP pattern for unit-testing your business logic: http://www.cerquit.com/blogs/post/Model-View-Presenter-Part-II---Unit-Testing.aspx
In a word, it is called Refactoring.
There are only a couple of reasons to put code into the UI:
- Interaction with a control on the form
- Validation although this can be placed in a business logic layer but I usually add a helper method in the UI (much easier)
All the other "Business Logic" code goes into another class called the business logic class. All the Database interaction code goes into a different class called the data access class.
When you write the code in the UI, simply ask yourself if the code is interacting with a control on the form. If not, it probably belongs in the other two classes.
Check out some books by Martin Fowler on refactoring like "Refactoring: Improving the Design of Existing Code". Another buzz word is seperation of concerns. I know you can do all this in one class but code becomes much more readable and easier to debug when it is seperated into classes as I described above.
精彩评论