在前幾天的文章中提到的DragonBalls的例子中,我們提到可以把List和他的方法抽到一個類別中,讓List和他的相關方法封裝載DragonBalls的類別中,消除Feature Envy的壞味道。
public class DragonBalls
{
private readonly List<DragonBall> _balls;
private const int MaxVisibleDistance = 100;
public DragonBalls(List<DragonBall> balls)
{
_balls = balls;
}
public IEnumerable<DragonBall> GetVisibleDragonBalls()
{
return _balls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
public DragonBall GetDragonBallAt(int index)
{
return _balls[index];
}
}
public class DragonBallRadar
{
private readonly DragonBalls _dragonBalls;
...
public RadarScreen Visible(int index)
{
return new RadarScreen()
{
...
DragonBalls = new List<DragonBall>() {_dragonBalls.GetDragonBallAt(index)},
...
};
}
public RadarScreen Visible()
{
return new RadarScreen()
{
...
DragonBalls = _dragonBalls.GetVisibleDragonBalls(),
...
};
}
...
}
但是同時也可以發現相對於List來說,DragonBalls的類別比較難看出他其實是封裝了List,可能是得移進類別方法本身才會知道,這個對於閱讀代碼來說有時候是會有一點不方便。
抽取類別之外,我們還能擴充方法來把List的操作封裝到List中,且保留直觀的List。
public static class DragonBallListExtension
{
private const int MaxVisibleDistance = 100;
public static IEnumerable<DragonBall> GetVisibleList(this List<DragonBall> dragonBalls)
{
return dragonBalls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
}
public class DragonBallRadar
{
private readonly List<DragonBall> _dragonBalls;
public DragonBallRadar(List<DragonBall> dragonBalls)
{
_dragonBalls = dragonBalls;
}
public RadarScreen Visible(int index)
{
return new RadarScreen()
{
DragonBalls = new List<DragonBall>() { _dragonBalls[index] },
...
};
}
public RadarScreen Visible()
{
return new RadarScreen()
{
DragonBalls = _dragonBalls.GetVisibleList(),
...
};
}
....
}
相較於上面抽取類別的方法,使用擴充方法來封裝List的操作之後,因為使用的類別還是List,所以也不必特別封裝GetDragonBallAt(int index)的操作了。整體代碼在維持相同的可讀性,但是代碼更簡潔了。
聽起來擴充方法好像是封裝集合操作的首選?其實也不一定,這還是得看實際情況來決定,假設當DragonBalls除了需要增加Add操作之外,還要紀錄最後一個加進List的龍珠。
public class DragonBalls
{
private readonly List<DragonBall> _balls;
private const int MaxVisibleDistance = 100;
private DragonBall _latestDragonBall = null;
public DragonBalls(List<DragonBall> balls)
{
_balls = balls;
}
public void AddBall(DragonBall ball)
{
_balls.Add(ball);
_latestDragonBall = dragonBall;
}
public IEnumerable<DragonBall> GetVisibleDragonBalls()
{
return _balls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
public DragonBall GetDragonBallAt(int index)
{
return _balls[index];
}
}
當類別除了操作集合之外,還會額外維護一些自己的狀態,此時擴充方法就變得不適合了。因為擴充方法需要static類別,但static類別無法拿來維護狀態。在這種情況下,除了原本的使用DragonBalls組合List之外,我們讓DragonBalls繼承List。
public class DragonBalls : List<DragonBall>
{
private const int MaxVisibleDistance = 100;
private DragonBall _latestAddBall = null;
public void AddBalls(DragonBall dragonBall)
{
Add(dragonBall);
_latestAddBall = dragonBall;
}
public IEnumerable<DragonBall> GetVisibleList()
{
return this.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
}
因為繼承了List,所以我們除了能保留List的所有操作之外,還能封裝屬於DragonBalls特有的操作,這個作法的代碼也比原本作法要來得短。
今天聊了兩個封裝集合的方法,加上使用組合的方式,一共有三個方法可以封裝集合。但是這三個作法其實並沒有哪一個比較好,哪一個比較差,更多是依照需求來選擇所需要的作法。
或許還有其他更多的使用使用情境,可以思考一下是不是有更多更好的方式來封裝集合與其相關操作。