SCSF. Chapter 3. D. Controllers

 

To fulfill 实现/履行/满足 its function as a container, a WorkItem needs a place to hold its business logic. In the early days of CAB, this was often done by deriving a new class from WorkItem, such as BankTellerWorkItem, and placing the business logic on that class. Experience with the first CAB applications showed that this pattern allowed the WorkItem class to get too complex. Everything that a logical subapplication had to do or to hold was concentrated 集中的 in just one class. It became the "God object," too large and complex to test or inspect, or even develop properly.

作为一个容器为了实现它的函数,一个WorkItem需要一个地方来保存它的业务逻辑。在CAB早期的日子里,这通常通过派生一个新的类来实现,像是BankTellerWorkItem,并且把业务逻辑放在这个类中。CAB程序的经验告诉我们这种模式使得WorkItem类太复杂了。所有子程序需要完成或保存的业务逻辑集中在仅仅一个类中。它成为了God对象,无论是测试还是检查都太大太复杂了,甚至对适当的开发而言。

By the time SCSF came out, the design philosophy 哲学 had shifted to 转入 recommend 推荐 separating the holding functionality from the doing functionality. Since the WorkItem already contained collections for holding objects, the idea emerged 浮现 that it should store its business logic in a separate class, which came to be called the controller of the WorkItem. The holding and the doing thus resided 存在 on different classes in the same way that the Win32 operating system provides a process for holding things and a thread for doing things. Placing the business logic in its own separate class made things much easier to understand, especially for inspecting and testing.

直到SCSF的出现,这种设计哲学转入推荐分离实现函数和保存函数。因为WorkItem已经包含了保存对象的集合,出现了这样一个想法把业务逻辑放到一个分离的类中,叫做WorkItem的controller。就像win32操作系统提供一个过程来保存和一个线程来做事,保存和做事现在存在于不同的类中。把业务逻辑放在它自己的分离类中使得事情变得更加简单去理解,特别是查看与测试。

This relationship between the controller (the business logic object, remember) and the WorkItem (the resource container object) was codified 编成法典 in the class ControlledWorkItem. SCSF creates this class in the Infrastructure.Interface project. Shown on the facing page, it represents a WorkItem that contains a controller, and it's actually pretty simple.

controller与workItem的关系被在ControlledWorkItem中编成法典。SCSF在Infrastructure.Interface项目中创建了这个类。它代表一个包含了一个controller的workItem,这相当的简单。

ControlledWorkItem derives from the WorkItem, so it contains all that considerable 重要的 functionality 功能. It is sealed, which means that it cannot be used as the basis for any further derivation. This is strong hint 暗示 to developers that the buck really ought 应当 to stop here.(the buck stops here 责任到此为止)

ControlledWorkItem派生于WorkItem,因此它包含了所有重要的功能。它是密封的,因此意味着不可以再次派生。这强烈的暗示责任到此为止。

You can see that the ControlledWorkItem class contains a controller object, which it creates when its OnBuiltUp method is called. Using the AddNew< > method of the WorkItem causes the new controller to receive CAB processing, such as dependency injection. The class of this controller object is passed as a generic parameter, called TController in the example. This controller class varies from 各不相同 one instance of a ControlledWorkItem to another, but the ControlledWorkItem that contains it is always identical 同一致 to all other ControlledWorkItems. The controller is stored internally 内部的 in a member variable and accessed through a read-only accessor property in the usual way.

ControlledWorkItem 类包含了一个controller对象,它创建于当OnBuiltUp方法被调用的时候。使用workItem的AddNew<>方法使得新Controller获得了CAB处理,像是依赖注入一样。controller对象的的类被当做泛型参数传递,在例子中被叫做TController。ControlledWorkItem 中的实例因controller 类而各不相同,但是它们包含的workItem都是一致的。controller被保存在一个成员变量中,并由一个只读属性作为入口。

public sealed class ControlledWorkItem <TController> : WorkItem
{
    private TController _controller;

    // Accessor property for the WorkItem's controller object

    public TController Controller
    {
        get { return _controller; }
    }

   // When the WorkItem object is finished being constructed
   // internally, then create the controller object. Save a reference
   // to it in a member variable, and also place it into the Items
   // collection of the WorkItem.

    public override void OnBuiltUp(string id)
    {
        base.OnBuiltUp(id);

        _controller = Items.AddNew<TController>();
    }
} 

The creation of a ControlledWorkItem and the most common means of 的手段 connecting it to the WorkItem chain are shown on the facing page. When the CAB module loader service loads a module, it uses the reflection API to examine the module's assembly for classes that derive from ModuleInit. When it finds such a class, the loader instantiates it. The SCSF Wizard generates modules with an injection constructor asking for injection of the root work item, which it stores in a private variable. Finally, the module loader service calls the ModuleInit object's Load method.

 ControlledWorkItem的创建和大部分连接到WorkItem链的手段我们都展示过了。当CAB module加载服务加载了一个module,它通过ModuleInit方法使用API的反射去检查module的程序集中的类。当它查找到这样的一个类的时候,加载器实例化它。SCSF向导生产module通过请求root work item的注入的构造函数,它储存在一个私有的变量中。最后,module加载服务请求ModuleInit对象的加载方法。

In the Load method we instantiate the ControlledWorkItem object by calling the method AddNew<> on the root work item, passing the class ControlledWorkItem as a generic parameter. This tells the root work item to create a new object of the specified class and place it into its WorkItems collection. Creating the ControlledWorkItem causes it to create its internal controller, as we saw on the previous page.

在加载方法中我们实例化ControlledWorkItem对象通过调用AddNew<>在root work item中,把类作为泛型参数传递给ControlledWorkItem。这告诉root work item去创建一个新的指定类的对象然后把它放到workItems的集合中。创建ControlledWorkItem导致它创建了自己的controller,我们将在下一节中看到。

After we create the new ControlledWorkItem with its controller, we need to initialize it. The WorkItem is already built up 加强 internally 内部的, but we haven't done anything yet with its business logic, which resides 属于 on the controller. We therefore call the controller's Run method, thereby telling it to jump up and start doing its thing, whatever that might be. Let's now turn our attention to the controller class itself.

在我们创建了ControlledWorkItem和它的Cotroller之后,我们需要初始化它。WorkItem已经内部的加强了,但是我们并没有完成任何业务逻辑,它属于controller。我们因此调用controller的Run方法,从而告诉它开始做它的事情,

Note

The name of the variable in the injection constructor is rootWorkItem, but this is sometimes a misnomer. The WorkItem that is passed is the WorkItem that caused the module to be loaded, passed as the first parameter to the module loader service's Load method. This happens most frequently in the normal application startup method CabApplication.LoadModules, where it is indeed the root WorkItem. However, it is possible, though somewhat unusual, for a child WorkItem to fetch the module loader service and use it to load modules (the WorkItems of which will be grandchildren of the root WorkItem). In this case, the child WorkItem will probably pass itself, not the root WorkItem, as the parameter to the Load method, in which case the injected WorkItem will be the child WorkItem, not the root. This parameter would be more accurately named parentWorkItem, even though the parent is almost always the root.


// The Module Loader service looks in the modules that it loads for
// classes that derive from ModuleInit. When it finds one, it
// instantiates it and calls Load.

public class Module : ModuleInit
{
    private WorkItem _rootWorkItem;

   // This injection constructor causes the root work item to be
   // injected into the new ModuleInit object. This allows the
   // WorkItem that this module will create to be connected to the
   // application's WorkItem chain.

    [InjectionConstructor]
    public Module([ServiceDependency] WorkItem rootWorkItem)
    {
        _rootWorkItem = rootWorkItem;
    }

    public override void Load()
    {
        base.Load();

           // Create a new ControlledWorkItem, placing it into the
           // RootWorkItem's collection of WorkItems. The process of
           // creating the ControlledWorkItem also creates an object
           // of class MyOwnModuleController, and attaches it to the
           // ControlledWorkItem.

        ControlledWorkItem<MyOwnController> workItem =
               rootWorkItem.WorkItems.AddNew<
               ControlledWorkItem<MyOwnController>>();

           // Call the Run method on the new WorkItem's Controller
           // so as to perform its initialization

        workItem.Controller.Run();
    }
}


					  

 

 

The controller for a WorkItem derives from the SCSF base class WorkItemController, shown on the facing page. It is generated by the SCSF and lives in the Infastructure. Interface project. It obtains the WorkItem to which it is connected by means of dependency injection.

The IWorkItemController interface contains the single method Run(), used at startup time, as we saw on the previous page. Apart from that, a controller can contain any code. You will note that the TController type in the definition of the ControlledWorkItem class does not specify that WorkItemController be the base class or that the controller implement the IWorkItemController. To do that, we would specify a derivation constraint (see Appendix A) written as follows:

public sealed class ControlledWorkItem <TController> : WorkItem
  where TController: WorkItemController

The base class goes to the trouble of exposing the ActionCatalogService (see Chapter 7) as an accessor property. Although this service is sometimes useful, it's not clear to me that this service is important enough to expose it in this special case and while not exposing other services in a similar manner.

// Base class for controller user by ControlledWorkItem class

public abstract class WorkItemController : IWorkItemController
{
    private WorkItem _workItem;

   // This dependency injection obtains for the controller a reference
   // to the WorkItem that contains it.

    [ServiceDependency]
    public WorkItem WorkItem
    {
        get { return _workItem; }
        set { _workItem = value; }
    }

    public IActionCatalogService ActionCatalogService
    {
        get { return _workItem.Services.Get<IActionCatalogService>(); }
    }

    public virtual void Run()
    {
    }
}

 

A sample controller class is shown on the facing page. The SCSF Wizard generates it with the class name ModuleController, but I find that a misnomer. The controller isn't tied to the module; it's tied to a WorkItem. The module is merely a convenient administrative container to facilitate being loaded by the module loader service. I find that changing the name to something that better describes what the controller's purpose is, such as PharmacyController, is more conducive to understanding as development proceeds.

The controller's Run method generally calls utility functions to do its internal setup, as shown on the facing page. This often entails adding services to the WorkItem (Chapter 2), creating and adding views and presenters to workspaces (Chapter 4), or modifying the shared user interface (Chapter 5). You certainly don't have to do it this way; you are free to modify it as needed, but simple cases might as well start out with it and see how far they get.

The controller class often contains methods that are command handlers for a shared user interface (Chapter 5) or event subscriptions (Chapter 6). It may also contain Action methods or conditions (Chapter 7) . Any code that is scoped at a WorkItem level, as opposed to an individual view or presenter, generally belongs on it.

public class PharmacyController : WorkItemController
{
    public override void Run()
    {

              // Load any additional services that the item might need.

           AddServices();

              // Extend the shell application's menu, as explained in
              // Chapter 5.

           ExtendMenu();

              // Extend the shell application's toolbar and status bar,
              // as explained in Chapter 5.

           ExtendToolStrip();

              // Create and wire up the Smart Parts, Views, and Presenters
              // used for displaying the user interface, as described in
              // Chapter 4.

           AddViews();
    }
posted @ 2017-02-21 15:10  plovjet  阅读(89)  评论(0)    收藏  举报