我們時常在各種場合使用List,例如交友軟體的黑名單、部落格的文章列表、購物車裡面的商品清單...等等。今天我們用個有趣一點的東西來舉例,假設我們要一個龍珠雷達,支援兩種顯示模式
public class DragonBallRadar
{
private readonly List<DragonBall> _dragonBalls;
private const int MaxVisibleDistance = 100;
...
public RadarScreen Visible(int index)
{
return new RadarScreen()
{
UserPosition = GetUserPosition(),
DragonBalls = new List<DragonBall>() { _dragonBalls[index] },
Scale = GetScale(),
Rotation = GetRotation(),
};
}
public RadarScreen Visible()
{
return new RadarScreen()
{
UserPosition = GetUserPosition(),
DragonBalls = GetVisibleDragonBalls(),
Scale = GetScale(),
Rotation = GetRotation(),
};
}
private IEnumerable<DragonBall> GetVisibleDragonBalls()
{
return _dragonBalls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
...
}
在DragonBallRadar中,我們可以發現MaxVisibleDistance和GetDragonBallsIn都只跟_dragonBalls有相依,有Feature Envy的壞味道,所以我們把_dragonBalls和其相關的方法、欄位抽取類別,提升DragonBallRadar與DragonBalls的內聚力。
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(),
...
};
}
...
}
再抽的過程中可以發現為了將_dragonBalls[index]也放到DragonBalls,我們不得不為他在建一個方法,讓DragonBallRadar用index存取特定龍珠,這會讓代碼稍微複雜一點。此時我們可以透過覆寫operator,讓DragonBalls可以維持原本的作法而不必多一個方法。
public class DragonBalls
{
private readonly List<DragonBall> _balls;
...
public DragonBall this[int index] => _balls[index];
}
public class DragonBallRadar
{
private readonly DragonBalls _dragonBalls;
...
public RadarScreen Visible(int index)
{
return new RadarScreen()
{
...
DragonBalls = new List<DragonBall>() { _dragonBalls[index] },
...
};
}
...
}
透過覆寫[] operator,讓我們可以即便抽取了List到某個類別之後,依舊可以維持我們熟悉的陣列用法,維持使用端的可讀性。關於覆寫operator不只能用在[ ]操作,還能用在其他像是常用的加法,我們之後會再針對這個多聊一些。
針對抽取把_dragonBalls抽了一個類別,我們可以發現GetVisibleDragonBall()方法名稱上有DragonBall,造成使用的時候變成變數名稱有dragonBall,方法名稱又出現了一次,名稱多餘的重複。
_dragonBalls.GetVisibleDragonBalls()
此時我們應該把方法名稱上多餘的DragonBall移除。
public class DragonBalls
{
...
public IEnumerable<DragonBall> GetVisibleList()
{
return _balls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
}
}
public class DragonBallRadar
{
private readonly DragonBalls _dragonBalls;
...
public RadarScreen Visible()
{
return new RadarScreen()
{
...
DragonBalls = _dragonBalls.GetVisibleList(),
...
};
}
...
}
當我們看到DragonBalls裡面有個GetVisibleList的方法,正常來說我們會預期他會拿到DragonsBall list,而不是其他類別。不太需要擔心從方法名稱上看不出來是要得到什麼,因為類別名稱很多時候會幫這個方法解釋。
透過代碼審查,持續思考代碼還能如何改善,從類別或方法的職責,或是他們的名稱,都是需要不斷的思考並調整,讓代碼越來越簡潔乾淨。