I'm creating a library in C# which will be used to query databases.
In order to use this library, a user first needs to tell the library what the database schema contains, by creating Schema
, Table
, and Column
objects.
A Schema
object consists of:
- A name, which is a
string
. - A collection of
Table
objects that it owns.
A Table
object consists of:
- A reference to the
Schema
object that owns it. - A name, which is a
string
. - A collection of
Column
objects that it owns.
Finally, a Column
object consists of:
- A reference to the
Table
object that owns it. - A name, which is a
string
. - A type, which is one of the values
String
,DateTime
, andReference
. - If the type is
Reference
, then a target, which is a reference to aTable
object within the sameSchema
.
I can think of a ton of ways to design a public interface for creating these objects, but all of them have flaws. What approach should I use?
Here are the ideas I've thought of:
Provide mutable classes only
The user can create Schema
, Table
, and Column
objects, and then change all properties of these objects after the fact.
Downsides:
- Ensuring that the states of these objects remain consistent with each other could be difficult.
- Since all of these objects are mutable, the user will have to keep in mind that they could change at any time. (And so will I, the author of the library!)
Provide immutable classes only: naive edition
The classes Schema
, Table
, and Column
are all immutable. All properties of these objects must be specified when they are created. You can't create a Schema
without specifying all of the Table
s it contains, and you can't create a Table
without specifying which Schema
it belongs to.
Fatal downside:
- Creating objects wouldn't actually be possible: you can't create a schema without creating its tables first, and you can't create tables without creating the schema first.
Provide immutable classes only: bottom-up construction
First, you create Column
objects. Next, you create Table
objects by passing in Column
objects. Finally, you create a Schema
object by passing in Table
objects. All of these classes are immutable.
Downsides:
- The
Column
object can't have a property indicating whichTable
the column belongs to, because theColumn
object is created before theTable
object is, and theColumn
object is immutable. Likewise, theTable
object can't have a property indicating whichSchema
it belongs to. - The "target table" property of a
Column
object will have to just be a string, not a reference to aTable
object. - The API user is forced to create objects in a specific order. What if you want to create tables first and add columns later?
Provide two kinds of immutable classes
Provide two families of classes. First, there are the SchemaDefinition
, TableDefinition
, and ColumnDefinition
classes, which work exactly as in the "bottom-up construction" approach.
There are also Schema
, Table
, and Column
classes, which are also immutable. These classes have all the properties that you could hope for: a Schema
has a property telling you about all of its Table
s, and a Table
has a property telling you which Schema
it belongs to.
The Schema
, Table
, and Column
classes have no public constructors. Instead, there's a SchemaFactory
class which takes a SchemaDefinition
and uses it to create a bunch of Schema
, Table
, and Column
objects, and then gives you the Schema
.
Downsides:
- There are two classes for each type of business object, instead of just one.
- The API user is still forced to create objects in a specific order.
Provide mutable and immutable classes
Provide two families of classes. First, there are the SchemaMutable
, TableMutable
, and ColumnMutable
classes, which work exactly as in the "provide mutable classes only" approach.
There are also Schema
, Table
, and Column
classes, which work just like in the "two kinds of immutable classes" approach. There's a factory class which takes a SchemaMutable
and gives you the Schema
.
Downsides:
- Again, there are two classes for each type of business object.
- And again, ensuring that the states of these objects remain consistent with each other could be difficult.
Table
object without specifying the schema.