C# 类与接口的树状遍历示例






4.96/5 (34投票s)
关于在使用分层树时使用类和接口的示例
引言
我一直在寻找一个清晰、实用的类和接口使用示例,但一直没有找到令人信服的。因此,我在这里基于英格兰的行政区划和区域展示我自己的示例——一方面是作为一个**类结构**,另一方面是作为几个附加结构(例如,一个**分层树**)。
我希望这有助于解释类和接口的不同可能性,以及同时使用它们各自优势的美妙之处。
背景
这里你可以找到一些关于类和接口的理论背景(感谢Rahman Mahmoodi)。
在我的例子中,我想从**面向对象的角度**定义“国家”、“大都市”、“郡”、“城市”、“村庄”、“自治市镇”或“区”的类,因为我想模拟接近现实生活的事物:见英格兰行政区划(维基百科)。
基于这些类,我想像这样“组装”英格兰及其国家、城市、自治市镇、村庄等。
var england = new Country("England");
var london = new Metropole("Greater London");
england.Counties = new County[]
{
new County("East Sussex"),
new County("Kent"),
new County("West Sussex"),
london
};
england.Counties[0].HumanSettlements = new HumanSettlement[] // East Sussex
{
new City("Eastbourne", 99412, "Eastbourne Railway Station"),
new City("Hastings", 90300),
new City("Newhaven", 12250)
};
england.Counties[1].HumanSettlements = new HumanSettlement[] // Kent
{
new Village("Ulcombe", "Peter Titchener"),
new City("Ashford", 74204),
new City("Dover", 28156, "Dover Priory"),
};
england.Counties[2].HumanSettlements = new HumanSettlement[] // West Sussex
{
new City("Chichester", 23731),
new City("Littlehampton", 27795),
new City("Shoreham-by-Sea", 19175),
new Village("West Chiltington", "Harvie Steele")
};
london.Boroughs = new Borough[] // Greater London
{
new Borough("Croydon", "Fairfield Halls", "East Croydon station"),
new Borough("Bromley", "London Fire Brigade", "Bromley South railway station"),
new Borough("City of Westminster", "Palace of Westminster"),
new Borough("Kingston upon Thames", "Coronation Stone"),
};
london.Boroughs[0].Districts = new District[] // Croydon
{
new District("Addington"),
new District("Forestdale"),
new District("Kenley"),
};
london.Boroughs[1].Districts = new District[] // Bromley
{
new District("Biggin Hill"),
new District("Chislehurst"),
new District("Crystal Palace"),
};
london.Boroughs[2].Districts = new District[] // City of Westminster
{
new District("Bayswater"),
new District("Paddington"),
new District("Pimlico"),
new District("Victoria")
};
但是,也有例如一个**分层角度**。英格兰国家包括像“东萨塞克斯”和“肯特”这样的郡,我们在其中找到城市和村庄(“伊斯特本”,“多佛”等)。英格兰还有一个大都市“大伦敦”。它包括像“克罗伊登”这样的自治市镇,而自治市镇又包含像“阿丁顿”,“贝特沃特”和“皮姆利科”这样的区域。
所以,我们不仅想通过它们特有的属性和方法来操作我们所有的类和对象。我们还想遍历英格兰的分层结构,例如在这样的列表中:
除此之外,我们还想关注一个**铁路视角**——比如说在城市和自治市镇中,以及可能还有一个**机场视角**——比如说在大城市和都市中。
我们该如何实现呢?
通过使用(抽象)类和接口,可以实现**面向对象视图**与**任何附加视图**相结合的期望结果。
面向对象视角
根据**面向对象视角**,我们定义我们的类,例如“Country
”、“County
”、“HumanSettlement
”、“City
”、“Village
”、“Metropole
”、“Borough
”和“District
”。
这里我们可以看到英格兰国家由郡组成。在郡中,我们发现了聚居地,即城市或村庄。郡的一种特殊类型可能是一个大都市,它由自治市镇组成——而自治市镇又由区域组成。
脚注:“HumanSettlement
”在这里被称为“抽象”,因为它*必须*是城市或村庄。相反,“County
”不是抽象的,因为它本身可以是一个郡*或*一个大都市(郡的一种特殊形式,UML“特化”)。
此外,国家是由郡*组成*的(UML“组合”:所有郡的总和*是*国家),但是郡拥有人类聚居地、农村地区和森林等的*聚合*(UML“聚合”)。
分层视角
在我们的**分层视角**中,我们将所有对象视为树节点。这使我们能够通过一个称为“树遍历”的过程一个接一个地遍历它们。因此,我们定义了一个名为“ITraversable
”的接口。这样,我们就使所有对象(视为节点)“可遍历”。
这里是上面的UML图,但现在我们不显示它们的结构关联,而是描绘我们如何将接口“ITraversable
”添加到所有对象中。
这里一个有趣之处在于,“Metropole
”、“City
”和“Village
”从它们的基类继承了接口。所以*所有*我们的类/对象现在都是“可遍历”的。
任何附加视角
我知道,我们也可以通过一个名为“Node
”的类作为所有树节点的基类来实现这一点。如果你愿意,也可以这样做。但现在我们添加了**附加视角**,从现在开始使用接口是有意义的。
这里你可以看到,大城市和都市可以通过飞机到达,而城市和自治市镇可以通过火车到达。通过引入接口“IByPlaneReachable
”,例如,我们可以将所有大城市和所有都市收集到一个列表“List<IByPlaneReachable> LocationsWithAirports
”中。从那时起,我们就可以从附加的“**机场视角**”处理特定的类/对象。对于“**铁路视角**”以及**任何其他视角**的实现也是如此。
因此,如果你想做类似的事情,你需要*多重继承*(C# 中未实现)*或*你使用接口。在这两种情况下,请注意所谓的“菱形问题”。
基础部分:类
让我们从一个典型的类开始,即“City
”。
public class City : HumanSettlement
{
public int Population { get; set; }
public string Station { get; set; }
public City(string Name, int Population, string Station = "")
: base(Name)
{
this.Population = Population;
this.Station = Station;
}
public override string ToString()
{
return String.Format("{0} ({1}) {2}", Name, Population.ToString("#,##0"), Station.ToUpper());
}
}
“City
”有一个“Population
”和一个“Station
”,并且在它的基类(“HumanSettlement
”)中有一个“Name
”。除此之外,我们还重写了“ToString()
”。
“Village
”与城市相似,但在这里我们只关注“Chairman
”(以展示继承和特化)。
public class Village : HumanSettlement
{
public string Chairman { get; set; }
public Village(string Name, string Chairman)
: base(Name)
{
this.Chairman = Chairman;
}
public override string ToString()
{
return String.Format("{0} ({1})", Name, Chairman);
}
}
所有其他类都极其相似,请直接查看源代码。最终,所有这些类使我们能够像开头所示那样组装英格兰。
到目前为止一切顺利。但现在我们为解决方案添加了**附加视角**。
神奇部分 1:接口
我们用于**分层树视图**和**铁路视图**的接口如下所示:
public interface ITraversable
{
string Name { get; }
ITraversable[] Children { get; }
}
public interface IByTrainReachable
{
string Station { get; set; }
string PrintStation();
}
有人曾用驾驶汽车的例子来形容接口:捷豹、保时捷和达契亚之间可能存在巨大差异。但我们都是通过使用它们的转向轮、换挡杆和踏板来驾驶它们的。因此,无论汽车在细节上有多大差异,我们都是通过它们标准的*接口*来操作它们的。
现在,我们的接口“ITraversable
”简单地定义了任何可遍历的对象(即我们分层树中的节点)必须拥有“Children
”和“Name
”。
“Children
”可能为空或为 null,但然后我们自动知道该特定对象是树的叶子。
我们的第二个接口“IByTrainReachable
”定义了任何可通过火车到达的对象必须拥有一个“Station
”和一个用于打印它的方法:“PrintStation()
”。
因此,我们可以这样使用接口:
public class Country : ITraversable // Now with tree interface
{
public string Name { get; set; }
public County[] Counties { get; set; }
// Now with tree children (=Counties)
public ITraversable[] Children { get { return Counties; } }
public Country(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name.ToUpper();
}
}
public class County : ITraversable // Now with tree interface
{
public string Name { get; set; }
public HumanSettlement[] HumanSettlements { get; set; }
// Now with tree children (=HumanSettlements)
public virtual ITraversable[] Children { get { return HumanSettlements; } }
public County(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name;
}
}
public abstract class HumanSettlement : ITraversable // Now with tree interface
{
public string Name { get; set; }
public ITraversable[] Children { get { return null; } } // Never has children
public HumanSettlement(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name.ToUpper();
}
}
public class City : HumanSettlement, IByTrainReachable // Now with railway interface
{
// and so on
public string Station { get; set; }
// and so on
public string PrintStation()
{
return String.Format("{0} {1}", Name, Station.ToUpper());
}
}
“Country
”、“County
”和“HumanSettlement
”现在实现了“ITraversable
”接口。这意味着它们必须实现“ITraversable Children[]
”和“Name
”。就这样。
“City
”从其基类“HumanSettlement”继承了接口“ITraversable
”,但它*另外*实现了接口“IByTrainReachable”。
我们对“Metropole
”、“District
”(等等)做了同样的事情。
public class Metropole : County, IByPlaneReachable // Now with airport interface
{
public Borough[] Boroughs { get; set; }
// Now with children (=Boroughs)
public override ITraversable[] Children { get { return Boroughs; } }
public Metropole(string Name)
: base(Name)
{
}
}
public class District : ITraversable // Now with tree interface
{
public string Name { get; set; }
public ITraversable[] Children { get { return null; } } // Never has children
public District(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name;
}
}
现在我们所有的类/对象都是“可遍历的”,并且其中一些还“可通过火车到达”、“可通过飞机到达”等等。
神奇部分 2:使用接口
当然,我们现在需要一个小程序(“Traversal()
”)来在树中从一个节点遍历到下一个节点。
public class Tree
{
public delegate void DoSomething(ITraversable Node);
public static void Traversal(ITraversable Node, DoSomething Action)
{
Action(Node); // Perform action
if (Node.Children != null)
foreach (var child in Node.Children)
Traversal(child, Action); // Recursion for each child
}
// Possible action for "DoSomething":
public static void List(ITraversable Node)
{
Console.WriteLine(Node.ToString());
}
// Possible action for "DoSomething"
public static void PrintStation(ITraversable Node)
{
if (Node is IByTrainReachable && (Node as IByTrainReachable).Station != "")
Console.WriteLine(Path() + ": " + (Node as IByTrainReachable).PrintStation());
}
}
在这里,我们从任何可遍历的对象开始(即参数“Node”代表任何实现接口“ITraversable
”的对象)。然后我们用这个对象(视为一个节点)“做一些事情”。这里我们简单地将“List()
”和“PrintStation()
”作为可能的操作。
之后,我们检查该节点是否具有子节点。如果有,我们递归地使用子节点调用方法本身。
静态方法“Tree.Traversal()
”然后像这样调用:
// Print the tree
Tree.Traversal(england, Tree.List);
事实上,这一行代码就开始了对树的整个遍历,从给定的节点“england
”开始,并对树中的每个节点——按正确的顺序——执行“Tree.List()
”。
因此,我们得到了从一开始就想要创建的完整的**树列表**。
ENGLAND
East Sussex
Eastbourne (99.412) EASTBOURNE RAILWAY STATION
Hastings (90.300)
Newhaven (12.250)
Kent
Ulcombe (Peter Titchener)
Ashford (74.204)
Dover (28.156) DOVER PRIORY
West Sussex
Chichester (23.731)
Littlehampton (27.795)
Shoreham-by-Sea (19.175)
West Chiltington (Harvie Steele)
Greater London
Croydon (Fairfield Halls) EAST CROYDON STATION
Addington
Forestdale
Kenley
Bromley (London Fire Brigade) BROMLEY SOUTH RAILWAY STATION
Biggin Hill
Chislehurst
Crystal Palace
City of Westminster (Palace of Westminster)
Bayswater
Paddington
Pimlico
Victoria
Kingston upon Thames (Coronation Stone)
但我们也可以查看**火车站**:
public class Tree
{
// ...
// Possible action for "DoSomething"
public static void PrintStation(ITraversable Node)
{
if (Node is IByTrainReachable && (Node as IByTrainReachable).Station != "")
Console.WriteLine(Path() + ": " + (Node as IByTrainReachable).PrintStation());
}
}
// Print all railway stations in the tree
Tree.Traversal(england, Tree.PrintStation);
England/East Sussex/Eastbourne: Eastbourne EASTBOURNE RAILWAY STATION
England/Kent/Dover: Dover DOVER PRIORY
England/Greater London/Croydon: Borough Croydon EAST CROYDON STATION
England/Greater London/Bromley: Borough Bromley BROMLEY SOUTH RAILWAY STATION
这难道不精彩吗?
结论
这是我关于类与接口的实用示例。它是否阐明了这个概念的可能性和史诗般的美学?欢迎任何评论!
历史
2014 年 8 月 2 日 - 发布。
2014 年 8 月 3 日 - 添加了多重继承方面。