views:

1034

answers:

6

I am developing an application trying to employ the Observer pattern. Basically I have a base form from which various components (forms) can be loaded.

The base form references each of the components and some of the components reference each other.

If I want one of the components to listen for events raised by the base form (perhaps from a menu etc) I can't seem to achieve this without needing to add a reference to the base form in the component. This causes a "circular reference".

Is it possible to listen/subscribe to events in projects which are not referenced?

A: 

You could go for the EventBroker pattern as seen in Microsoft's Patterns and Practices Library.

However I'm not sure myself if that's so good a pattern, personally I prefer to create architecture where objects don't reference one another (always a parent->child scenario) and deal with the dependency problems instead of ignoring them.

Quibblesome
+1  A: 

You can solve this in various ways. One simple way is to have a special class which knows about the base form and the various components. This class is responsible for creating the form and the components. Since it knows about them, it can attach event handlers to the appropriate events. It essentially just "plugs in" event handler methods on the components to the events on the base form.

Another way is to define an interface with events which will be implemented by the main form. The components can be passed an instance of this interface in their constructors. They can then attach to the event handlers. Thus the components know about the interface only, not the base form. This is an application of the "depend on abstractions, not implementations" principle. The base form, in this case, would implement the interface and have knowledge of the components, passing itself to them when they are constructed. Thus the dependency is one-way.

The ultimate solution, however, is to use a dependency injection container such as StructureMap. You'd have a configuration method which registers the base form class as the default implementor of the interface, and the various component classes. StructureMap can then create instances of classes as required, injecting the interface into the constructors automatically.

Mike Scott
+1  A: 

Microsoft has a "Managed Extensibility Framework (MEF)" in development that might work for you. From the MEF overview:

MEF provides a standard way for the host application to expose itself and consume external extensions. Extensions, by their nature, can be reused amongst different applications. However, an extension could still be implemented in a way that is application-specific. Extensions themselves can depend on one another and MEF will make sure they are wired together in the correct order (another thing you won't have to worry about).

The overview and downloads for MEF are at http://www.codeplex.com/MEF/Wiki/View.aspx?title=Overview&referringTitle=Home

AJ
+1  A: 

Your framework should define the interfaces that are used to wire things up (note that events can be defined in interfaces, so you can promote the events to the interface). Mike Scott's advice of "depend on abstractions, not implementations" is spot on, but I'd be a little stronger here - you should program by contract and use a layered design.

Alternatively, you can use interfaces like INotifyPropertyChanged which provide a string that can be used to retrieve information via reflection, but that is such a fragile way to work and should be a last resort.

Daniel Paull
A: 

Are you concerned about circular references or circular dependencies? Circular references (a run time issue) are very difficult to avoid when using the observer pattern. Circular dependencies (a design time issue) can always be got rid of.

Typical usage of the observer pattern in c# is for the observer have a reference to a publisher object and to register itself with the event using something like:

publisherObject.SomeEvent += MyEventHandler();

So the observer already has a reference to the publisherObject and what happens in the background is that publisherObject gets sent (and stores) a reference to the observer's event handler. So unless you are going to drop the reference to publisherObject immediately, you're stuck with a circular reference.

This is normally only a problem when you want the garbage collector to tidy up unused objects. The "hidden" reference to the observer's event handler inside publisherObject is enough to prevent the observer being garbage collected. This is sometimes referred to as the lapsed listener problem. Easiest way round it is to put event unsubscriptions in the observer's Dispose() method (and remember to call it when you get rid of the observer).

You can get round this, but it generally adds quite a lot of complexity that may not be warranted in a smallish app. Previous posters have already suggested the EventBroker, MEF and dependency injection routes.

If you're more concerned about circular dependencies, then the simplest answer is a strict parent-child hierarchy as Quarrelsome suggested. The parent (observer) always knows about its children so can call properties and methods on them directly if it needs to. The children (publishers) should know nothing about their parent. They communicate upwards purely via events and function return values. Communication between children is then routed via a common parent (events on the way up, method calls on the way down).

Note that you've still got circular references due to the way the event mechanism works (so be careful about disposing) but you don't have circular dependencies. The child can even be in completely different assembly that doesn't have a design time reference the one containing the parent.

Note that you can be a bit flexible about what you consider the parent and what the child. Just because your main form creates a sub form doesn't mean you have to define the parent-child communications in that direction. A typical plug-in style architecture may have the main form creating instances of the plugins. But once created, the communications will probably treat the plugin as the parent/observer and the main form as the publisher/event source.

Nigel Hawkins
A: 

Just create a project with a class for the Event:

public class BaseFormEventClass
{
    public EventHandler<EventArgs> BaseFormDidSomething;
}

Then reference this project from both, the baseform project and the components project. Let the baseform create an instance of the eventclass and give it to all components he loads:

public class MyComponent
{
    public MyComponent(BaseFormEventClass eventClass)
    {
        eventClass.BaseFormDidSomething += this.EventClass_BaseFormDidSometing;
    }
    // ...
}

public class BaseForm
{
    private BaseFormEventClass eventClass = new BaseFormEventClass();

    private void LoadComponents()
    {
        MyComponent component1 = new MyComponent(this.eventClass);
    }

    private void RaiseBaseFormDidSomething()
    {
        EventHandler<EventArgs> handler = eventClass.BaseFormDidSomething;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}
Hinek