开发者

Tight coupling with related classes?

I have custom-made wizard system which 开发者_StackOverflow社区so far has been quite suitable. For most wizards, the pages can be constructed in a fairly generic fashion, so only one class implements those types of pages. However, some need to be custom-designed, so there is an abstract base class for those kinds of pages. Due to some deficiencies in the VS designer, the page can't itself be a UI control and also be an abstract class with generic parameters (those exist for fluent programming). So one option I've followed is to implement the page across two classes, one for the UI (which derives from UserControl and can be designed) and one for the page. The page contains an instance of the UI control and embeds that in itself for display. Not optimal, but it works.

Now, a problem arises from this set up: there is a tight coupling between the UI control class and the page class. This wouldn't normally be that much of an issue except that pages can be derived from to create specialized versions of the pages. So the derived control and page classes are also tightly coupled with themselves. So when I have member variables, properties and methods on the control and page classes that are typed for the control or page class, respectively (i.e., the control class will have a property Page which points to the page the control is embedded in), we run into a big problem with derived classes. Each derived class must somehow change the type of these members. What I had thought of doing was including a generic type parameter that would allow those members to be generically typed:

public class BaseControl<TControl, TPage>
    where TPage : BasePage<TPage, TControl>
    where TControl : BaseControl<TControl, TPage> {
    public TPage Page { get { ... } set { ... } }
    ...
}

public class BasePage<TPage, TControl>
    where TPage : BasePage<TPage, TControl>
    where TControl : BaseControl<TControl, TPage> {
    public TControl Control { get { ... } set { ... }
    ...
}

public class DerivedControl<TControl, TPage> : BaseControl<TControl, TPage>
    where TControl : DerivedControl<TControl, TPage>
    where TPage : DerivedPage<TPage, TControl> { }

public class DerivedPage<TPage, TControl> : BasePage<TPage, TControl>
    where TControl : DerivedControl<TControl, TPage>
    where TPage : DerivedPage<TPage, TControl> { }

Obviously, this is the kind of C++-style garbage that I'd like to avoid. Besides the ugliness, it creates an actual problem wherein one must create sealed "leaf" classes to work around the infinite recursion problem that CRTP brings us.

And yet the alternatives are also unappealing. I could have those members have a fixed type of the base type and do casting everywhere. This doesn't enforce type safety and it requires pointless casts (I already know that the type will be such-and-such, but the compiler doesn't). I could suck it up and combine the page and the control class into one, with no abstract or generic type parameters. That ruins the fluent programming system, or makes it much harder to implement, with much repetition (I'll explain if needed, but let's just assume that that part of my design is legitimate).

As such, I'm a bit at a loss for how to do this in a sane manner. Everything I've considered so far can be made to work, but the code smell is horrible. Is there something I'm missing, some way of doing this that has so far eluded me?


I have very similar code in my Protocol Buffers port, where a message type and a builder for that message type are coupled.

Basically, you're trying to express a relationship which C# generics makes awkward.

In my case most of this is hidden in generated code, so the dev doesn't need to actually do very much... but it's still ugly. I did look for alternatives, but I didn't find any. I think what you've got may well be as good as you can get, I'm afraid... assuming what you wrote about the VS constraints and your other requirements is correct. If you find yourself able to work in a very different design, that's great - but if you need the two related types, I think all of this "junk" is required :(

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜