12,063,006 members (33,539 online)
Class diagrams are important in developing a model for a software system before you actually start programming your software. They provide designers, architects, and developers a graphical interface to manipulate methods, properties, fields, events for data structures like classes, interfaces, structs, and more. In this article, we will use what I call ModelStudio as a designer to help you create your model structures before directly diving into the code, and while we are interacting with this designer, it should be noted that the code will be updated in the background and later auto-generated using the
I think that modeling your code first is a good first step in OOP and designing a great software application architecture. If you are looking for a Reflective solution, then you may want to take a look at something like Sacha Barber's 100% Reflective Class Diagram Creation Tool. For the diagrammer, which I won't discuss in this article, I modified and used the
ClassDrawerContainerPanel and dependent classes from Barber's tool. I also want to acknowledge the use of Chagosoft's cDOMx Class Builder for ideas as well.
The ModelStudio application uses the ModelStudio Framework as we refer to create Visual Basic and C# code documents. To use ModelStudio is fairly straightforward.
Namespaceobject is created.
There is one note that I need to add about my purpose for putting this resource tool together. Starting with Visual Studio 2005, Microsoft has included what they call the Class Designer. The Class Designer tool may work for your organization, especially if you have the super duper Visual Studio Team System. However, what I dislike about the Class Designer tool are the 'little things'. For instance, I like for all generated classes to initialize any custom types during class construction.
Books = New List(of Book)
Class Designer neglects to do that. Secondly, wouldn't it be nice to have completed setters and getters for all your properties along with the associated fields auto declared? Class Designer neglects to do this as well. Instead, what Class Designer provides you with is only a prototyped class property by default.
Public Overridable Property Name() As String Get End Get Set(ByVal value As String) End Set End Property
Private m_Name As String Public Overridable Property Name() As String Get Return Me.m_Name End Get Set(ByVal value As String) Me.m_Name = value End Set End Property
The ModelStudio Framework implements a simple design that first encapsulates the class information into a 'facade' of simple classes. Here we use the term facade in its most liberal sense. Definition: "a superficial appearance or illusion of something. (E.g.: They managed somehow to maintain a facade of wealth)". These facade classes and the data within each is then "translated", which we will discuss later, to create the actual CodeDom classes for code generation.
Description: This is an example facade class that will be used to create a new
using) statement for your generated classes.
Public Class CodeDomImport Implements ICodeMember Private m_Import As String = "System.Text" Private m_Parent As Object ''' Initialize a new instance of the CodeDomImport object ''' The fully qualified Namespace that will be imported ie: System.Text Sub New(ByVal ImportName As String) m_Import = ImportName End Sub ''' The fully qualified Namespace that represents the import ie: System.text Public Property Name() As String Implements ICodeMember.Name Get Return m_Import End Get Set(ByVal value As String) m_Import = value End Set End Property Public Property Parent() As Object Get Return m_Parent End Get Set(ByVal value As Object) m_Parent= value End Set End Property End Class
This is the business end of the whole thing. Here is where the actual code gets translated, prepared, and generated to code files.
Basically, the structure of this class is not ideal. However, for the purposes of this article, everything gets done inside this one class. Translation takes place when we return a regular CodeDOM
Public Function GetNamespace(ByVal _Namespace As CodeDomNamespace) As CodeNamespace Dim _space As CodeDomNamespace = _Namespace Dim _ns As CodeNamespace = _Namespace.DOMNameSpace '==Imports If _space.Imports.Count > 0 Then For Each import As CodeDomImport In _space.Imports Dim im As New CodeNamespaceImport(import.Name) _ns.Imports.Add(im) Next End If .... End Function
Now we need to prepare the code for output the way we want it. During what we call the preparation stage, you will be using the following CodeDom namespaces:
Imports System.CodeDom Imports System.CodeDom.Compiler
To prepare classes to initialize all custom types during class construction, we use the following code:
Dim mcdClass As CodeTypeDeclaration = _Class.DOMClass 'Constructors For Each meth As CodeDomMethod In _Class.Constructors If meth.Parameters.Count > 0 Or _Class.CustomTypes.Count > 0 Then Dim cnstr As New CodeDom.CodeConstructor For Each param As CodeDomParameter In meth.Parameters cnstr.Parameters.Add(New _ CodeParameterDeclarationExpression(param.Type, param.Name)) Next ' Here is where custom initialization begins For Each p As CodeDomProperty In _Class.CustomTypes Dim this As New CodeThisReferenceExpression() Dim leftExpression As New CodePropertyReferenceExpression(this, p.Name) Dim rightExpression As New _ CodeObjectCreateExpression(New CodeTypeReference(p.Type)) Dim assign As New CodeAssignStatement(leftExpression, rightExpression) cnstr.Statements.Add(assign) Next p mcdClass.Members.Add(cnstr) End If Next meth
Now, you can change your constructor to initialize any type that you want by simply adjusting the above commands to meet your own individual preferences.
Here is where we actually output what we've translated and prepared. Obviously, we are going to generate VB code here, but there is also a similar C# generation method as well.
Private Sub GenerateVBFile(ByVal n As CodeNamespace, ByVal OutputDirectory As String) m_Unit = New CodeCompileUnit m_Unit.Namespaces.Add(n) ' Generate the code with the VB code provider. Dim provider As Microsoft.VisualBasic.VBCodeProvider = _ New Microsoft.VisualBasic.VBCodeProvider ' Build the output file name. For Each c As CodeDom.CodeTypeDeclaration In n.Types Dim tempUnit As New CodeCompileUnit tempUnit.Namespaces.Add(New CodeDom.CodeNamespace(n.Name)) tempUnit.Namespaces(0).Types.Add(c) ' Build the output file name. Dim sourceFile As String = OutputDirectory + _ Path.DirectorySeparatorChar + c.Name + "." + provider.FileExtension ' Create a TextWriter to a StreamWriter to an output file. Using tw As New IndentedTextWriter(New StreamWriter(sourceFile, False), " ") ' Generate source code using the code provider. provider.GenerateCodeFromCompileUnit(tempUnit, tw, New CodeGeneratorOptions()) ' Close the output file. tw.Close() End Using Next c End Sub
Pretty simple, just sets up the private members and calls
GenerateCodeFromCompileUnit from the
VBCodeProvider and spits out a class, which should look like:
Namespace Classroom Public Class Student Private m_Name As String Private m_StudentID As Integer Private m_MyTeacher As Teacher Private m_MyChair As Chair Private m_IsSpecial As Boolean Private m_Books As List(Of Book) Private Sub New() MyBase.New Me.MyTeacher = New Teacher Me.MyChair = New Chair Me.Books = New List(Of Book) End Sub Public Overridable Property Name() As [String] Get Return Me.m_Name End Get Set Me.m_Name = value End Set End Property ... End Class End Namespace
The last part we have to do is import all of the generated classes into our project solution. Build it and run it. Congratulations, now the real work begins.