Page 19 - MSDN Magazine, May 2019
P. 19
The Working Programmer TED NEWARD Coding Naked: Naked Collections
Welcome back, NOFers. Last time, I augmented the Speaker domain type with a number of properties, along with a number of annotations and conventions about those properties that provide hints (or, to be more honest, directions) to the UI as to how to validate or present those properties to the user. One thing I didn’t discuss, however, is how a given domain object can have references to more than one of something. For example, Speakers often have multiple Talks they can give, or can profess expertise in one or more Topics. NOF refers to these as “collections,” and there are a few rules surrounding how they work that are a little different from the previous conversation.
Let’s see about giving Speakers some Talks and Topics, shall we?
Naked Concepts
To start with, abandon all arrays, all ye who enter here. NOF doesn’t make use of arrays for collection properties, and instead relies entirely on collection objects (IEnumerable<T>-derived) to hold zero-to-many of some other type. The NOF manual strongly rec- ommends that these collections be strongly typed (using generics), and NOF doesn’t allow for multiple associations of value types (like strings, enumerated types and so on) because NOF believes that if the type is something “important” enough to warrant association, it should be a full-fledged domain type.
Thus, for example, if I want to capture the notion of a Topic (like “C#,” “Java” or “Distributed Systems”) in the conference system, where other programming approaches may allow you to get away with a simple “list-of-strings” as a property type, NOF insists that Topic be a full domain object type (which is to say, a public class with prop- erties), complete with its own domain rules. It’s reasonable that the list of Topics might be a fixed set, however, so I’ll seed the database with the complete set of Topics that my conference wants to consider.
Likewise, although a Talk could be just a title, it’s really a series of things: a title, a description, a Topic (to which it belongs or refers), and is given by one (or more) Speakers. Clearly, I have a little domain modeling in front of me yet.
Naked Collections
In many ways, the easiest way to begin is with the domain classes for Talk and Topic themselves, absent any connections between them (or Speakers). By now, much of what I write here for each of these should be pretty trivial and straightforward, as Figure 1 shows.
So far, this is pretty straightforward. (There’re obviously other things that could and/or should be added to each of these two classes, but this gets the point across pretty well.) The one new attribute used, [Bounded], is an indication to NOF that the complete (and immutable) list of instances can and should be held in memory on
the client, and presented to the user as a dropdown list from which to choose. Correspondingly, then, the full list of Topics needs to be established in the database, which is easiest done in the DbInitializer class from the “SeedData” project, in the Seed method (as discussed in prior columns in this series), shown in Figure 2.
This will provide a (rather small, but useful) list of Topics from which to work. By the way, if you’re playing the home game and writing the code by hand, remember to add the TalkRepository to the main menu by adding it to the MainMenus method in Naked- ObjectsRunSettings.cs in the Server project. In addition, make
Figure 1 Domain Classes for Talk and Topic
public class Talk {
[NakedObjectsIgnore]
public virtual int Id { get; set; }
[Title]
[StringLength(100, MinimumLength = 1,
ErrorMessage = "Talks must have an abstract")] public virtual string Title { get; set; }
[StringLength(400, MinimumLength = 1, ErrorMessage = "Talks must have an abstract")]
public virtual string Abstract { get; set; } }
public class TalkRepository {
public IDomainObjectContainer Container { set; protected get; }
public IQueryable<Talk> AllTopics() {
return Container.Instances<Talk>(); }
}
[Bounded]
public class Topic {
[NakedObjectsIgnore]
public virtual int Id { get; set; }
[Title]
[StringLength(100, MinimumLength = 1,
ErrorMessage = "Topics must have a name")] public virtual string Name { get; set; }
[StringLength(400, MinimumLength = 1,
ErrorMessage = "Topics must have a description")]
public virtual string Description { get; set; } }
public class TopicRepository {
public IDomainObjectContainer Container { set; protected get; }
public IQueryable<Topic> AllTopics() {
return Container.Instances<Topic>(); }
}
May 2019 13