継承、構成、集玄

いく぀かの新しいトピック、コンセプト、プログラミングツヌルを扱うこずに決めたこずがよくありたす。私はむンタヌネット䞊のさたざたなサむトで次々ず蚘事を読みたした。 たた、トピックが耇雑な堎合、これらの蚘事を読んで理解に䞀歩近づかないかもしれたせん。 そしお、突然、掞察を䞎え、すべおのパズルがたずめられた蚘事がありたす。 そのような蚘事を他の蚘事ず区別するものを刀断するこずは困難です。 正しく遞択された単語、最適なプレれンテヌションロゞック、たたはより関連性の高い䟋。 私の蚘事がCの新しい単語たたは最高の教育蚘事であるず刀明するふりはしたせん。 しかし、おそらく誰かにずっおは、議論される抂念を理解し、蚘憶し、正しく適甚し始めるこずができるものになるでしょう。

オブゞェクト指向プログラミング蚀語では、クラス間の盞互䜜甚を敎理する3぀の方法がありたす。 継承ずは、継承クラスに芪クラスのすべおのフィヌルドずメ゜ッドがあり、原則ずしおいく぀かの新しい機胜たたは/およびフィヌルドが远加される堎合です。 継承は「is」ずいう蚀葉で蚘述されたす。 乗甚車は車です。 圌が圌の盞続人になるならば、それは自然です。

```class Vehicle { bool hasWheels; } class Car : Vehicle { string model = "Porshe"; int numberOfWheels = 4 }``` 

関連付けずは、あるクラスがフィヌルドの1぀ずしお別のクラスを含む堎合です。 関連付けは、「has」ずいう蚀葉で説明されたす。 車にぱンゞンがありたす。 圌が゚ンゞンの盞続人にならないのは圓然ですただし、状況によっおはこのようなアヌキテクチャも可胜です。

関連付けの2぀の特定のケヌスが区別されたす構成ず集玄。

構成ずは、゚ンゞンが車ずは別に存圚しない堎合です。 車の䜜成時に䜜成され、車によっお完党に制埡されたす。 兞型的な䟋では、゚ンゞンむンスタンスは自動車デザむナヌで䜜成されたす。

 ``` class Engine { int power; public Engine(int p) { power = p; } } class Car { string model = "Porshe"; Engine engine; public Car() { this.engine = new Engine(360); } } ``` 

集玄ずは、゚ンゞンむンスタンスがコヌド内の別の堎所で䜜成され、パラメヌタヌずしおカヌデザむナヌに枡される堎合です。

 ``` class Engine { int power; public Engine(int p) { power = p; } } class Car { string model = "Porshe"; Engine engine; public Car(Engine someEngine) { this.engine = someEngine; } } Engine goodEngine = new Engine(360); Car porshe = new Car(goodEngine); ``` 

クラス間の盞互䜜甚を敎理する特定の方法の利点に぀いおは議論がありたすが、抜象的な芏則はありたせん。 開発者は、基本ロゞック「is」たたは「has」に基づいお䜕らかの方法を遞択したすが、これらの方法が䞎えるおよび課す可胜性ず制限も考慮したす。 これらの機胜ず制限を確認するために、䟋を䜜成しおみたした。 コヌドがコンパクトなたたであるようにシンプルであるだけでなく、3぀のメ゜ッドすべおを1぀のプログラム内で適甚できるように十分に開発されおいたす。 そしお、最も重芁なこずは、この䟋をできるだけ抜象的なものにしようずしたこずです。すべおのオブゞェクトずむンスタンスは理解可胜であり、具䜓的なものです。

簡単なゲヌム、タンクバトルを曞きたしょう。 2぀の戊車が遊んでいたす。 圌らは健康をれロに萜ずした人を亀互に撃ち、倱いたす。 ゲヌムにはさたざたな皮類のシェルずアヌマヌがありたす。 ダメヌゞを䞎えるためには、たず敵の戊車を攻撃し、次に敵の鎧を突砎する必芁がありたす。 アヌマヌが砎損しおいない堎合、損傷はありたせん。 ゲヌムのロゞックは、「石paper玙」の原則に基づいおいたす。぀たり、あるタむプの鎧は特定のタむプのシェルには十分耐性がありたすが、他のシェルは䞍十分です。 さらに、鎧をよく貫通するシェルは小さな「鎧」ダメヌゞを䞎え、逆に、最も「臎呜的な」シェルは鎧を貫通する可胜性が䜎くなりたす。

銃の簡単なクラスを䜜成したしょう。 口埄ずバレル長の2぀のプラむベヌトフィヌルドがありたす。 ダメヌゞは口埄に䟝存し、䞀郚は装甲を突砎する胜力に䟝存したす。 バレル長から-粟床。

 ``` public class Gun { private int caliber; private int barrelLength; } ``` 

銃のコンストラクタヌも䜜成したす。

 ``` public Gun(int cal, int length) { this.caliber = cal; this.barrelLength = length; } ``` 

他のクラスから口埄を取埗するためのメ゜ッドを䜜成したしょう

 ``` public int GetCaliber() { return this.caliber; } ``` 

タヌゲットをヒットするためには、タヌゲットをヒットするこずずアヌマヌを突砎するこずの2぀のこずを行う必芁があるこずを芚えおいたすか したがっお、最初の攻撃は銃が担圓したす。ヒットです。 そのため、ブヌル倉数メ゜ッドIsOnTargetを䜜成したす。これは、ランダム倉数サむコロを取り、結果を返したす。ヒットするかどうか

 ``` public bool IsOnTarget(int dice) { return (barrelLength + dice) > 100; } ``` 

銃のクラス党䜓は次のずおりです。

 ``` public class Gun { private int caliber; private int barrelLength; public Gun(int cal, int length) { this.caliber = cal; this.barrelLength = length; } public int GetCaliber() { return this.caliber; } public bool IsOnTarget(int dice) { return (barrelLength + dice) > 100; } } ``` 

ここでシェルを䜜成したす-これは継承を適甚​​する最も明らかなケヌスですが、その䞭での集玄も適甚可胜です。 シェルには独自の特性がありたす。 䞀郚の仮想シェルは存圚したせん。 したがっお、クラスを抜象化したす。 圌を文字列フィヌルド「タむプ」にしたす。

砲匟は砲甚に䜜られおいたす。 特定の銃。 ある口埄の発射䜓は、別の口埄の倧砲を発射したせん。 したがっお、発射物フィヌルドリンクを銃のむンスタンスに远加したす。 コンストラクタを䜜成したす。

 ``` public abstract class Ammo { Gun gun; public string type; public Ammo(Gun someGun, string type) { gun = someGun; this.type = type; } } ``` 

ここでは集蚈を適甚したした。 どこかに銃が䜜成されたす。 次に、銃ぞのポむンタを持぀シェルがこの銃甚に䜜成されたす。

特定の皮類のシェルは、抜象シェルの盞続人になりたす。 盞続人は単に芪のメ゜ッドを継承できたすが、オヌバヌラむドするこずもできたす。぀たり、芪メ゜ッドずは異なる動䜜をしたす。 しかし、発射䜓には倚くのメ゜ッドが必芁であるこずは確かです。 発射物はダメヌゞを䞎えなければなりたせん。 GetDamageメ゜ッドは、単に3倍した口埄を返したす。 䞀般に、発射䜓の損傷は口埄に䟝存したす。 しかし、このメ゜ッドは子クラスで再定矩されたす鎧をよく貫通するシェルは、通垞「鎧」のダメヌゞが少ないこずを芚えおおいおください。子クラスでメ゜ッドを再定矩できるようにするには、virtualずいう蚀葉を䜿甚したす。

 ``` public virtual int GetDamage() { //TO OVERRIDE: add logic of variable damage depending on Ammo type return gun.GetCaliber()*3; } ``` 

発射䜓は、鎧を貫通する必芁がありたすたたは少なくずも貫通しようずしたす。 䞀般的に、装甲を貫通する胜力は口埄にも䟝存したすたずえば、初期速床などですが、耇雑にするこずはありたせん。 したがっお、メ゜ッドは口埄を返したす。 ぀たり、倧たかに蚀っお、発射䜓はその口埄ず同じ厚さの装甲を貫通できたす。 このメ゜ッドは、子クラスではオヌバヌラむドされたせん。

 ``` public int GetPenetration() { return gun.GetCaliber(); } ``` 

さらに、䟿利なデバッグずコン゜ヌル出力の敎理のために、ToStringメ゜ッドを远加するこずは理にかなっおいたす。これにより、単玔にシェルの皮類ず口埄を確認できたす。

 ``` public override string ToString() { return $" " + type + "    " + gun.GetCaliber(); } ``` 

次に、抜象的なシェルを継承するさたざたなタむプのシェルを䜜成したす高爆発性、环積、サブキャリバヌ。 高爆発物は最倧のダメヌゞを䞎え、环積-少ない、準口埄-さらに少ない。 子クラスにはフィヌルドがなく、ベヌスシェルのコンストラクタヌを呌び出し、それに銃ず文字列型を枡したす。 GetDamageメ゜ッドは子クラスでオヌバヌラむドされたす。デフォルトず比范しお損傷を増加たたは枛少させる係数が远加されたす。

高爆発性デフォルトの損傷

 ``` public class HECartridge : Ammo { public HECartridge(Gun someGun) : base(someGun, "") { } public override int GetDamage() { return (int)(base.GetDamage()); } } ``` 

环積デフォルトのダメヌゞx 0.6

 ``` public class HEATCartridge : Ammo { public HEATCartridge(Gun someGun) : base(someGun, "") { } public override int GetDamage() { return (int)(base.GetDamage() * 0.6); } } ``` 

サブキャリバヌデフォルトのダメヌゞx 0.3

 ``` public class APCartridge : Ammo { public APCartridge(Gun someGun) : base(someGun, "") { } public override int GetDamage() { return (int)(base.GetDamage() * 0.3); } } ``` 

オヌバヌラむドされたGetDamageメ゜ッドは、基本クラスメ゜ッドも呌び出すこずに泚意しおください。 ぀たり、メ゜ッドをオヌバヌラむドするこずにより、baseキヌワヌドを䜿甚しおデフォルトのメ゜ッドにアクセスする機胜も保持されたす。

そのため、シェルには集玄基本クラスの銃ず継承の䞡方を䜿甚したした。
次に、戊車の装甲を䜜成したす。 ここでは継承のみが適甚されたす。 鎧には厚さがありたす。 したがっお、抜象鎧クラスには、厚さフィヌルドず、子クラスの䜜成時に定矩されるタむプ文字列フィヌルドがありたす。

 ``` public abstract class Armour { public int thickness; public string type; public Armour(int thickness, string type) { this.thickness = thickness; this.type = type; } } ``` 

ゲヌム内の鎧は、それらが壊れおいるかどうかを刀断したす。 したがっお、予玄のタむプに応じお、子䌚瀟で再定矩されるメ゜ッドは1぀だけになりたす。

 ``` public virtual bool IsPenetrated(Ammo projectile) { return projectile.GetDamage() > thickness; } ``` 

そしお、それらが壊れおいるかどうかは、どの発射䜓が到着したかによっお異なりたす。デフォルトの堎合、どの口埄の堎合です。 したがっお、このメ゜ッドは発射物のむンスタンスを取埗し、ブヌル倀の結果砎損しおいるかどうかを返したす。 いく぀かのタむプのアヌマヌ-抜象アヌマヌの盞続人を䜜成したしょう。 コヌドの皮類は1぀だけです。ロゞックはシェルずほが同じです。 同皮の装甲は、高爆発性のシェルをよく保持したすが、䞍十分です-サブキャリバヌ。 したがっお、高い貫通力を持぀サブキャリバヌの発射䜓が到着するず、蚈算では装甲が薄くなりたす。 など各タむプのアヌマヌには、特定の発射䜓に察する抵抗係数の独自のセットがありたす。

 ``` public class HArmour : Armour { public HArmour(int thickness) : base(thickness, "") { } public override bool IsPenetrated(Ammo projectile) { if (projectile is HECartridge) { // ,      return projectile.GetPenetration() > this.thickness * 1.2; } else if (projectile is HEATCartridge) { // ,     return projectile.GetPenetration() > this.thickness * 1; } else { // ,     return projectile.GetPenetration() > this.thickness * 0.7; } } } ``` 

ここでは、ポリモヌフィズムがもたらす驚異の1぀を䜿甚したす。 このメ゜ッドはあらゆる発射物を受け入れたす。 眲名は、子クラスではなく基本クラスを瀺したす。 しかし、メ゜ッド内では、どのような皮類のシェルが飛んだか、぀たりどのタむプかがわかりたす。 これに応じお、このロゞックたたはそのロゞックを実装したす。 シェルに継承を適甚​​せず、シェルのタむプの3぀の䞀意のクラスを䜜成しただけの堎合、アヌマヌを突砎するための別のテストを手配する必芁がありたす。 ゲヌム内のシェルのタむプず同数のオヌバヌロヌドメ゜ッドを蚘述し、到着したシェルの皮類に応じおそのうちの1぀を呌び出す必芁がありたす。 これもかなり゚レガントですが、この蚘事のトピックには関係ありたせん。

これでタンクを䜜成する準備ができたした。 タンクには継承はありたせんが、構成ず集玄がありたす。 もちろん、戊車には名前がありたす。 戊車には銃がありたす集合䜓。 私たちのゲヌムでは、各移動の前に戊車が鎧を「倉曎」できるず仮定したす-いずれかの皮類の鎧を遞択したす。 このため、戊車には鎧の皮類のリストがありたす。 戊車には匟薬庫がありたす。これは、戊車の蚭蚈者䜜曲で䜜成された砲匟で満たされる砲匟のリストです。 戊車は健康状態になり呜䞭するず枛少したす、戊車には珟圚遞択されおいる鎧ず珟圚遞択されおいる発射物がありたす。

 ``` public class Panzer { private string model; private Gun gun; private List<Armour> armours; private List<Ammo> ammos; private int health; public Ammo LoadedAmmo { get; set; } public Armour SelectedArmour { get; set; } } ``` 

戊車蚭蚈者が倚かれ少なかれコンパクトであり続けるために、適切な厚さの3皮類の装甲を远加し、匟薬パックに3皮類のそれぞれのシェルを10個充填する2぀の補助的なプラむベヌトメ゜ッドを䜜成したす。

 ``` private void AddArmours(int armourWidth) { armours.Add(new SArmour(armourWidth)); armours.Add(new HArmour(armourWidth)); armours.Add(new CArmour(armourWidth)); } private void LoadAmmos() { for(int i = 0; i < 10; i++) { ammos.Add(new APCartridge(this.gun)); ammos.Add(new HEATCartridge(this.gun)); ammos.Add(new HECartridge(this.gun)); } } ``` 

戊車蚭蚈者は次のようになりたす。

 ``` public Panzer(string name, Gun someGun, int armourWidth, int h) { model = name; gun = someGun; health = h; armours = new List<Armour>(); ammos = new List<Ammo>(); AddArmours(armourWidth); LoadAmmos(); LoadedAmmo = null; SelectedArmour = armours[0]; //  -   }``` 

ここで再び倚型の可胜性を䜿甚しおいるこずに泚意しおください。 リストにはAmmoデヌタタむプ芪シェルがあるため、私たちの匟薬はあらゆるタむプのシェルを保持したす。 継承されず、独自の皮類のシェルを䜜成した堎合、シェルの皮類ごずに個別のリストを䜜成する必芁がありたす。

戊車のナヌザヌむンタヌフェむスは、装甲の遞択、銃の装填、射撃の3぀の方法で構成されおいたす。

アヌマヌを遞択

 ``` public void SelectArmour(string type) { for (int i = 0; i < armours.Count; i++) { if (armours[i].type == type) { SelectedArmour = armours[i]; break; } } } ``` 

銃を充電したす。

 ``` public void LoadGun(string type) { for(int i = 0; i < ammos.Count; i++) { if(ammos[i].type == type) { LoadedAmmo = ammos[i]; Console.WriteLine("!"); return; } } Console.WriteLine($", , " + type + " !"); } ``` 

冒頭で述べたように、この䟋では、垞に念頭に眮いおおく必芁のある抜象的な抂念から遠く離れようずしたした。 したがっお、私たちず䞀緒の発射物の各むンスタンスは、戊闘の前に戊闘パックに入れられた物理的な発射物ず同じです。 その結果、シェルは最も䞍適切な瞬間に終了する可胜性がありたす

撮圱するには

 ``` public Ammo Shoot() { if (LoadedAmmo != null) { Ammo firedAmmo = (Ammo)LoadedAmmo.Clone(); ammos.Remove(LoadedAmmo); LoadedAmmo = null; Random rnd = new Random(); int dice = rnd.Next(0, 100); bool hit = this.gun.IsOnTarget(dice); if (this.gun.IsOnTarget(dice)) { Console.WriteLine("!"); return firedAmmo; } else { Console.WriteLine("!"); return null; } } else Console.WriteLine(" "); return null; } ``` 

ここで-より詳现に。 たず、銃が装填されおいるかどうかを確認するチェックがありたす。 第二に、銃身から飛び出した砲匟はこの戊車にはもはや存圚せず、倧砲や匟薬庫にはありたせん。 しかし、物理的にはただ存圚しおいたす-それは目暙に向かっお飛ぶ。 そしお、ヒットするず、アヌマヌの貫通力ずタヌゲットぞのダメヌゞの蚈算に参加したす。 したがっお、このシェルを新しい倉数Ammo firedAmmoに保存したす。 次の行では、この発射䜓はこの戊車には存圚しなくなるため、発射䜓の基本クラスにIClonableむンタヌフェむスを䜿甚する必芁がありたす。

 ``` public abstract class Ammo : ICloneable ``` 

このむンタヌフェむスには、Cloneメ゜ッドの実装が必芁です。 ここにありたす

 ``` public object Clone() { return this.MemberwiseClone(); } ``` 

これですべおが非垞に珟実的になりたした。ショットが発射されるず、サむコロが生成され、銃はIsOnTargetメ゜ッドでヒットを蚈算し、ヒットがある堎合、Shootメ゜ッドは発射物のむンスタンスを返し、それが芋぀からない堎合はnullを返したす。

戊車の最埌の方法は、敵の砲匟が呜䞭したずきの挙動です。

 ``` public void HandleHit(Ammo projectile) { if (SelectedArmour.IsPenetrated(projectile)) { this.health -= projectile.GetDamage(); } else Console.WriteLine("  ."); } ``` 

再びそのすべおの栄光の倚型。 シェルが飛んでくる。 どれでも。 遞択された装甲ず発射䜓のタむプに基づいお、装甲は突砎されるかされたせん。 ピアスされた堎合、特定のシェルタむプのGetDamageメ゜ッドが呌び出されたす。

すべお準備完了です。 コン゜ヌルたたは非コン゜ヌル出力を蚘述するだけで、ナヌザヌむンタヌフェむスが提䟛され、プレむダヌの亀互の動きが実装されたす。

たずめるず。 継承、構成、および集玄を䜿甚するプログラムを䜜成したしたが、違いを理解し、思い出したこずを願っおいたす。 ポリモヌフィズムの可胜性を積極的に掻甚したした。たず、子クラスのむンスタンスを芪のデヌタ型を持぀リストに結合できる堎合、そしお次に、芪むンスタンスをパラメヌタヌずしお取埗するメ゜ッドを䜜成するこずで、その䞭で子のメ゜ッドが呌び出されたす テキストの過皋で、可胜な代替実装-継承を集玄に眮き換える-に぀いお蚀及したしたが、普遍的なレシピはありたせん。 実装では、継承により、ゲヌムに新しい詳现を簡単に远加できたした。 たずえば、新しいタむプの発射物を远加するには、次のもののみが必芁です。


同様に、別の皮類のアヌマヌを远加するには、このタむプを蚘述し、ナヌザヌむンタヌフェむスにアむテムを远加するだけです。 他のクラスたたはメ゜ッドの倉曎は必芁ありたせん。

以䞋にクラスの図を瀺したす。



ゲヌムの最終コヌドでは、テキストで䜿甚されたすべおの「マゞックナンバヌ」が個別の静的構成クラスに配眮されたす。 コヌドの任意のフラグメントから静的クラスのパブリックフィヌルドにアクセスでき、そのむンスタンスを䜜成する必芁はありたせん䞍可胜です。 これはどのように芋えるかです

 ``` public static class Config { public static List<string> ammoTypes = new List<string> { "", "", "" }; public static List<string> armourTypes = new List<string> { "", "", "" }; //   - ,    ,      public static int _gunTrashold = 100; //       public static int _defaultDamage = 3; //      public static double _HEDamage = 1.0; public static double _HEATDamage = 0.6; public static double _APDamage = 0.3; //   // : //     ,      -  1.2 public static double _HArmour_VS_HE = 1.2; //     ,      -  1.0 public static double _HArmour_VS_HEAT = 1.0; //     ,      -  0.7 public static double _HArmour_VS_AP = 0.7; //   //     ,      -  1 public static double _Armour_VS_HE = 1.0; //     ,      -  0.8 public static double _Armour_VS_HEAT = 0.8; //     ,      -  1.2 public static double _Armour_VS_AP = 1.2; //   //     ,      -  0.8 public static double _SArmour_VS_HE = 0.8; //     ,      -  1.2 public static double _SArmour_VS_HEAT = 1.2; //     ,      -  1 public static double _SArmour_VS_AP = 1.0; } ``` 

そしお、このクラスのおかげで、クラスずメ゜ッドをさらに深くするこずなく、ここでのみパラメヌタヌを倉曎しお、さらに調敎するこずができたす。 たずえば、サブキャリバヌの発射䜓が匷すぎるこずが刀明した堎合、Configの1桁を倉曎したす。
すべおのゲヌムコヌドはここで芋るこずができたす 。

Source: https://habr.com/ru/post/J354046/


All Articles