官术网_书友最值得收藏!

Open/closed principle

Previously, we had a look at the single responsibility principle. Hand in hand with this is the open/closed principle.

Bertrand Meyer stated that software entities (classes, modules, functions, and so on):

  • Should be open for extension
  • Should be closed for modification

What exactly does this mean? Let's take the PlayerStatistics class as an example. Inside this class, you know that we have a method to calculate the strike rate of a particular player. This is included in the class because it inherits from the Statistics abstract class. That is correct, but the fact that the CalculateStrikeRate(Player player) method caters for two player types (all-rounders and batsmen) is already a hint of a problem.

Let's assume that we have introduced new player types—different bowler types (for example, fast bowlers and spin bowlers). In order for us to accommodate the new player type, we must change the code in the CalculateStrikeRate() method.

What if we wanted to pass through a collection of batsmen to calculate the average strike rate between all of them? We would need to modify the CalculateStrikeRate() method again to accommodate this. As time goes by and the complexities increase, it will become very difficult to keep on catering for different player types that need the strike rate calculation. This means that our CalculateStrikeRate() method is open for modification and closed for extension. This is in contravention of the principles stated previously in the bullet list.

So, what can we do to fix this? In truth, we are already halfway there. Start by creating a new Bowler class in the Classes folder:

using cricketScoreTrack.BaseClasses; 
using cricketScoreTrack.Interfaces; 
 
namespace cricketScoreTrack.Classes 
{ 
    public class Bowler : Player, IBowler 
    { 
        #region Player 
        public override string FirstName { get; set; } 
        public override string LastName { get; set; } 
        public override int Age { get; set; } 
        public override string Bio { get; set; } 
        #endregion 
 
        #region IBowler 
        public double BowlerSpeed { get; set; } 
        public string BowlerType { get; set; }  
        public int BowlerBallsBowled { get; set; } 
        public int BowlerMaidens { get; set; } 
        public int BowlerWickets { get; set; } 
        public double BowlerEconomy => BowlerRunsConceded / 
BowlerOversBowled; public int BowlerRunsConceded { get; set; } public int BowlerOversBowled { get; set; } #endregion } }

You can see how easy it is to construct new player types—we have only to tell the class that it needs to inherit the Player abstract class and implement the IBowler interface.

Next, we need to create new player statistics classes—namely, BatsmanStatistics, BowlerStatistics, and AllRounderStatistics. The code for the BatsmanStatistics class will look as follows:

using cricketScoreTrack.BaseClasses; 
using System; 
 
namespace cricketScoreTrack.Classes 
{ 
    public class BatsmanStatistics : Statistics 
    { 
        public override int CalculatePlayerRank(Player player) 
        { 
            return 1; 
        } 
 
        public override double CalculateStrikeRate(Player player) 
        { 
            if (player is Batsman batsman) 
            { 
                return (batsman.BatsmanRuns * 100) / 
batsman.BatsmanBallsFaced; } else throw new ArgumentException("Incorrect argument
supplied"); } } }

Next, we add the AllRounderStatistics class:

using cricketScoreTrack.BaseClasses; 
using System; 
 
namespace cricketScoreTrack.Classes 
{ 
    public class AllRounderStatistics : Statistics 
    { 
        public override int CalculatePlayerRank(Player player) 
        { 
            return 1; 
        } 
 
        public override double CalculateStrikeRate(Player player) 
        { 
            if (player is AllRounder allrounder) 
            { 
                return (allrounder.BowlerBallsBowled / 
allrounder.BowlerWickets); } else throw new ArgumentException("Incorrect argument
supplied"); } } }

Lastly, we add the new player type statistics class called BowlerStatistics:

using cricketScoreTrack.BaseClasses; 
using System; 
 
namespace cricketScoreTrack.Classes 
{ 
    public class BowlerStatistics : Statistics 
    { 
        public override int CalculatePlayerRank(Player player) 
        { 
            return 1; 
        } 
 
        public override double CalculateStrikeRate(Player player) 
        { 
            if (player is Bowler bowler) 
            { 
                return (bowler.BowlerBallsBowled / 
bowler.BowlerWickets); } else throw new ArgumentException("Incorrect argument
supplied"); } } }

Moving the responsibility of calculating the strike rates for all players away from the PlayerStatistics class makes our code cleaner and more robust. In fact, the PlayerStatistics class is all but obsolete.

By adding another player type, we were able to easily define the logic of this new player by implementing the correct interface. Our code is smaller and easier to maintain. We can see this by comparing the previous code for CalculateStrikeRate() with the new code we wrote.

To illustrate more clearly, take a look at the following code:

public override double CalculateStrikeRate(Player player) 
{             
    switch (player) 
    { 
        case AllRounder allrounder: 
            return (allrounder.BowlerBallsBowled / 
allrounder.BowlerWickets); case Batsman batsman: return (batsman.BatsmanRuns * 100) /
batsman.BatsmanBallsFaced; case Bowler bowler: return (bowler.BowlerBallsBowled / bowler.BowlerWickets); default: throw new ArgumentException("Incorrect argument
supplied"); } }

The preceding code is much more complex and less maintainable than the following:

public override double CalculateStrikeRate(Player player) 
{ 
    if (player is Bowler bowler) 
    { 
        return (bowler.BowlerBallsBowled / bowler.BowlerWickets); 
    } 
    else 
        throw new ArgumentException("Incorrect argument supplied"); 
} 

The benefit of creating a BowlerStatistics class, for example, is that you know that throughout the class we are only dealing with a bowler and nothing else...a single responsibility that is open for extension without having to modify the code.

主站蜘蛛池模板: 永福县| 河东区| 赤水市| 濮阳市| 濉溪县| 黔南| 利辛县| 靖边县| 龙南县| 富民县| 白山市| 忻城县| 嘉善县| 罗江县| 博罗县| 东兴市| 贡嘎县| 晋州市| 梁河县| 山东| 武安市| 贵南县| 洛隆县| 日喀则市| 西乌| 嘉义市| 甘泉县| 彝良县| 西盟| 兴安县| 镇康县| 五台县| 新闻| 阿勒泰市| 定陶县| 漠河县| 浪卡子县| 鲁山县| 沾化县| 邢台市| 江油市|