LINQ and the AutoCAD .NET API (Part 9)
Brushing up the API
This is the ninth in a series of posts on LINQ an the AutoCAD .NET API. Here's a complete list of posts in this series.
Introduction
This is part 9 of LINQ and the AutoCAD .NET API. Today we want to do some refactoring to get a nicer API.
Naming things is hard*
At the moment our main entry point to our API is the GeneralHelper
class. GeneralHelper
is not really a good name for an API class. The purpose of the GeneralHelper
class is to provide access to the database of the current document, so maybe something like Database
would be a better name. But unfortunately Database
is already the class name of the AutoCAD database object we're dealing with, so to avoid a name conflict, we could, for the sake of simplicity, add a prefix like Acad
. AcadDatabase
, FTW!
First we have a look at how we create instances of AcadDatabase
:
In the constructor we simply assign the current database to the db
variable and start a new transaction.
Loading a database from file
So right now we're dealing with the database of the active document. But what about reading a DWG file into a new database? The database object provides a method ReadDwgFile(string, FileOpenMode, bool, string)
to open a drawing database from an DWG file. We could incorporate this as a constructor overload into our AcadDatabase
:
OK, so what did we add? A new constructor with three arguments: the name of the file to open, a bool that indicates whether to open the file for read or write, and a password, if the database is password protected. The other two new constructors are just convenience overloads that set default values for read/write mode and the password.
And voila, now we can handle external databases as well!
But there's one thing we can improve: from an API perspective, there's a problem with constructors. As the name of a constructor is the name of the class, constructors don't have a way to indicate via their name what they are actually doing. In contrast, the method we're using for reading in the DWG file, ReadDwgFile
, is perfectly named. The method name clearly indicates the purpose of the method, to read a DWG file (ReadDwgFile
). That's not possible with constructors.
Of course we could carefully name the arguments, so that one could deduce from the arguments what the constructor is doing. But what about a default constructor like What about AcadDatabase()
? A default constructor doesn't have any arguments. In the case of AcadDatabase()
, one can only guess that it is using the database of the active document.
One way around the problem would be, that we add XML documentation that indicates the purpose of the constructor, but we should strive for an API that is in a way self-documenting by the class and method names.
Factory methods
So how can we overcome the problem with constructors? One way to go is to make all constructors private and to introduce static factory methods, which create and return the objects.
For example, instead of using our newly introduced constructor that loads a database form a DWG file, we could convert it into a static method that has a more meaningful name, something like FromFile
:
We now can open an external file using the following command:
This may seem to be just a minor change, but using factory methods we now have a way to give the default constructor a more expressive name, like FromActiveDocument
:
After this refactoring, there's only one constructor left, which is private and only called from the factory methods. And the factory methods are:
FromActiveDocument
which, as the name implies (and that's what we want), creates an instance ofAcadDatabase
from the active documentFromFile
which creates an instance ofAcadDatabase
from a given DWG file
Importing a block
Importing a block into the active database is a common use of loading a drawing database from a file. We could add an Import(BlockTableRecord)
method to our BlockContainer
that encapsulates the import action:
Finally we have a pretty expressive way of importing a block from a drawing file into the active drawing:
Except for having the import functionality encapsulated in a single method, the nice thing is that Import
makes our client code semantically more intuitive: The block container has a method named Import that takes the actual block object we want to import as an argument.
What's next
And that's it for today's post. In the next post we optimize our code.