문제

I have a composite class structure with many classes involved. This structure needs to be traversed for many different reasons (validation, cloning, exporting as xml etc) so it makes sense to write use a visitor pattern. Given the following class structure

class Owner
{
    public string Name { get; set; }
    public List<Owned> Liked { get; private set; }
    public List<Owned> Disliked { get; private set; }

    public Owner()
    {
        this.Liked = new List<Owned>();
        this.Disliked = new List<Owned>();
    }
}

class Owned
{
    public string Name { get; set; }
}

How should such a visitor pattern be implemented if I want to produce XML like this

<owner>
    <name>Owner 1</name>
    <likedThings>
        <owned>
            <name>Liked thing 1</name>
        </owned>
        <owned>
            <name>Liked thing 2</name>
        </owned>
    </likedThings>
    <dislikedThings>
        <owned>
            <name>Disliked thing 1</name>
        </owned>
        <owned>
            <name>Disliked thing 2</name>
        </owned>
    </dislikedThings>
</owner>

The first thing that concerns me is that usually I'd have VisitOwner and VisitOwned, and this would work fine for validation but in XML I would need to wrap the Owned objects within their respective likedThings or dislikedThings XML nodes.

The second thing that concerns me is that I would want a compile time error for each existing visitor implementation that has not yet implemented an action for any new part of the composition (e.g. a new property "List<Owned> SmellyThings")

도움이 되었습니까?

해결책

The Visitor Pattern lets you build the structure for the double-dispatch side of the problem, helping you deal with the complexity that is due to your inheritance structure of your model. However, the classical form of the pattern does nothing to deal with the complexity that is due to the compositional structure of your model, especially when the same class is used multiple times in different capacities.

In your case, a solution must work out both of these complexities - on one hand, you have the Owner vs. the Owned; on the other side, you have the Liked, Disliked, and whatever else that you plan to add.

The task of dealing with the composition side is traditionally given to implementations of the visitor, not to the interface. However, the compiler would not be able to help you find violators that fail to process the newly relationships. However, you can combine the visitor pattern with the Template Method Pattern to create a hybrid solution that deals with both problems.

Here is a skeleton of what you can do:

// This is a run-of-the-mill visitor
interface IVisitor {
    void VisitOwner(Owner owner);
    void VisitOwned(Owned owned);
}
// This is a base visitor class; it is abstract
abstract class DefaultVisitor : IVisitor {
    public void VisitOwner(Owner owner) {
        BeginOwner(owner);
        BeginLiked();
        foreach (var owned in owner.Liked) {
            owned.Accept(this);
        }
        EndLiked();
        BeginDisliked();
        foreach (var owned in owner.Disliked) {
            owned.Accept(this);
        }
        EndDisliked();
        EndOwner(owner);
    }
    public void VisitOwned(Owned owned) {
        BeginOwned(owned);
        EndOwned(owned);
    }
    public abstract void BeginOwner(Owner owner);
    public abstract void EndOwner(Owner owner);
    public abstract void BeginOwned(Owned owned);
    public abstract void EndOwned(Owned owned);
    public abstract void BeginLiked();
    public abstract void EndLiked();
    public abstract void BeginDisliked();
    public abstract void EndDisliked();
}

The advantage of structuring your code like that is that the compiler is now on the hook for checking implementations of DefaultVisitor for presence of all abstract methods; the disadvantage is that these implementations will have to provide eight implementations instead of two.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top