How can I store different objects in a single list

送分小仙女□ 提交于 2019-12-19 03:46:10

问题


I have two classes an Arc class and a Line class

public class Arc
{
     protected double startx;
     protected double starty;
     protected double endx;
     protected double endy;
     protected double radius;

     public Arc(){}
}
public class Line
{
     protected double startx;
     protected double starty;
     protected double endx;
     protected double endy;
     protected double length;
     public Line(){}
}

But I want to store arcs and lines in the same list, so I tried an interface like this

public interface Entity
{
     double StartX();
     double StratY();
     double EndX();
     double EndY();
}

Then I added the appropriate methods to each class and added the code to use the interface. Now I can add both types of objects to a list, but I want to get the length from a line object and don't want to add a length method to the arc or the interface. Is my only option to cast the line object back to a line object like this?

List<Entity> entities = new List<Entity>();
entities.Add(new Line(10,10,5,5));
Line myLine = (Line)Entities[0]
double length = myLine.Length();

*Assuming I have all the proper methods in the line class.
Or is there a better/different way to do this?


回答1:


Or is there a better/different way to do this?

If your objects descend from a common class, then you can store them in the same collection. In order to do anything useful with your objects without throwing away type safety, you'd need to implement the visitor pattern:

public interface EntityVisitor
{
    void Visit(Arc arc);
    void Visit(Line line);
}

public abstract class Entity
{
    public abstract void Accept(EntityVisitor visitor);
}

public class Arc : Entity
{
    protected double startx;
    protected double starty;
    protected double endx;
    protected double endy;
    protected double radius;

    public override void Accept(EntityVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Line : Entity
{
    protected double startx;
    protected double starty;
    protected double endx;
    protected double endy;
    protected double length;

    public override void Accept(EntityVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Once that's in place, you create an instance of EntityVisitor whenever you need to do something useful with your list:

class EntityTypeCounter : EntityVisitor
{
    public int TotalLines { get; private set; }
    public int TotalArcs { get; private set; }

    #region EntityVisitor Members

    public void Visit(Arc arc) { TotalArcs++; }
    public void Visit(Line line) { TotalLines++; }

    #endregion
}

class Program
{
    static void Main(string[] args)
    {
        Entity[] entities = new Entity[] { new Arc(), new Line(), new Arc(), new Arc(), new Line() };
        EntityTypeCounter counter = entities.Aggregate(
            new EntityTypeCounter(),
            (acc, item) => { item.Accept(acc); return acc; });

        Console.WriteLine("TotalLines: {0}", counter.TotalLines);
        Console.WriteLine("TotalArcs: {0}", counter.TotalArcs);
    }
}

And for what its worth, if your open to trying new languages, then F#'s tagged unions + pattern matching are a handy alternative to the visitor pattern.




回答2:


If you're in .NET 3.5 or above, you can make this a bit less ugly this way:

List<Entity> entities = new List<Entity>();

// add some lines and some arcs

var lines = entities.OfType<Line>();

Then you just loop through lines, which will contain all the lines (strongly-typed as Lines) and nothing else.

I'm not saying this is the best approach; I'm only saying this is one way to do what you're doing. I agree with Shmoopty that it's an architecture problem.




回答3:


Since Arc and Line share data (startx and some other fields), I suggest you use a common abstract class as parent class rather than an interface. For example, Figure.

The cast is okay, although I would rather recommend:

Line myLine = Entities[0] as Line;

It will return null if Entities[0] cannot be converted to a Line, rather than throwing an exception. You will be able to check whether myLine is null afterward.




回答4:


Yes, it is the only way, given your constraints.

I would suggest adding length to the interface (since arc does have a length).

The formula can be found here.

Or alternatively you could add the method to the interface, and have it throw a NotImplementedException.




回答5:


Have the interface implement a "Size" property (or call it "magnitue", or "Range". . .)

This maps to the Arc's radius, and to the lines length.

Then you can get Entity.Size.




回答6:


It depends how you want to treat Arcs when you get them out of the list. If you try and cast an Arc to a Line you will get a runtime error, so for starters you should check if the Entity you're working with is a Line.

One way to handle Arcs is to use the Null Object Pattern. It might make sense to add a length method to Arc that returns 0. That way the code that retrieves objects from the list doesn't have to care what kind they are.



来源:https://stackoverflow.com/questions/1848312/how-can-i-store-different-objects-in-a-single-list

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!