MARIO_1 चुनौती में यह पीच और मारियो को एक मंच से दूसरे मंच पर कूदने के बारे में था। उनमें से प्रत्येक के पास विशिष्ट गुण हैं लेकिन सबसे ऊपर सामान्य क्रियाएं हैं! इसलिए हम इस फिक्स में देखेंगे कि 2 टूल का उपयोग करके अपने कोड को कैसे संरचित किया जाए: इंटरफेस और यह अमूर्त तरीके के माध्यम सेपरंपरा.
कार्यक्रम में:
यूएमएल में कक्षा संरचनाएं
हम यहां सटीक अर्थों में यूएमएल आरेख पर नहीं हैं, लेकिन यह आपको यह कल्पना करने की अनुमति देगा कि फ़ाइलें कैसे व्यवस्थित की जाएंगी:
इसलिए हम बाईं ओर इनहेरिटेंस तंत्र के साथ फ़ाइलों का संगठन और दाईं ओर इंटरफ़ेस तंत्र के साथ फ़ाइलों का संगठन पाते हैं।
जब आप करीब से देखते हैं तो बहुत कम अंतर होता है। सार्वजनिक रूप से पढ़ने योग्य संपत्ति के साथ विरासत पक्ष पर एक निर्माण है जबकि इंटरफ़ेस में एक getName विधि है। हम स्पष्टीकरण में देखेंगे कि इस अंतर का औचित्य क्या है।
बीच में लेवल क्लास, संरचनात्मक दृष्टिकोण से दोनों यांत्रिकी के लिए समान है। विधियों के विवरण में (केवल) अंतर होगा।
यदि आपने अभी तक चुनौती को स्वयं हल नहीं किया है, तो आप इसे हल करने के लिए इस आरेख का उपयोग कर सकते हैं।
PHP में सार विधि और विरासत
इसलिए हमारी 2 कक्षाएं मारियो और पीच एक अमूर्त वर्ग जम्पर से विरासत में मिलेंगी।
एक अमूर्त वर्ग एक ऐसा वर्ग है जिसे त्वरित नहीं किया जा सकता है, यह विरासत के संदर्भ में उपयोगी है, लेकिन कोई इसे स्वयं त्वरित करने के लिए “अपर्याप्त” मान सकता है।
इसलिए हम घातक त्रुटि उत्पन्न होने के जोखिम पर कभी भी “नया जम्पर ()” नहीं कर सकते।
यहाँ सार जम्पर वर्ग के लिए कोड है:
abstract class Jumper
{
public function __construct(
public readonly string $name
) {}
abstract public function canJump(int $gapLength): bool;
}
थोड़ा सा स्पष्टीकरण:
- मेरे पास घोषणा में “वर्ग” कीवर्ड से पहले “सार” कीवर्ड है
- मेरा कंस्ट्रक्टर आपको सार्वजनिक और केवल पढ़ने योग्य प्रॉपर्टी $name को इंस्टेंट करने की अनुमति देता है जो एक कैरेक्टर स्ट्रिंग है
- canJump विधि में यह “सार” कीवर्ड भी है। हम इसके पैरामीटर और इसके रिटर्न प्रकार की घोषणा करते हैं और कुछ नहीं। मेरे पास बॉडी ब्रेसेस विधि नहीं है।
अब मारियो वर्ग:
class Mario extends Jumper
{
public function __construct()
{
parent::__construct('M');
}
public function canJump(int $gapLength): bool
{
return $gapLength <= 3;
}
}
थोड़ा सा स्पष्टीकरण:
- इसलिए मारियो वर्ग “एक्सटेंड्स” कीवर्ड का उपयोग करके जम्पर वर्ग का विस्तार करता है।
- मारियो का अपना स्वयं का कंस्ट्रक्टर है, जो $name को “M” मान से प्रारंभ करने के लिए मूल वर्ग के कंस्ट्रक्टर, इसलिए जम्पर, को कॉल करता है।
- यदि मैं मारियो में कैनजंप विधि नहीं बनाता, तो मेरी आईडीई (तब मेरा कोड) मुझे एक त्रुटि दिखाता है।
- इसलिए मैंने जम्पर (पैरामीटर और रिटर्न प्रकार) में इसकी घोषणा का ईमानदारी से सम्मान करते हुए यह विधि बनाई है। दूसरी ओर, मैं सामग्री के रूप में “मैं जो चाहता हूँ” डालता हूँ। यहां मैं जांचता हूं कि स्थान का आकार 3 से कम या उसके बराबर है क्योंकि मारियो 1, 2 या 3 आकार के स्थान को छोड़ सकता है।
यहाँ पीच वर्ग के लिए कोड है:
class Peach extends Jumper
{
public function __construct()
{
parent::__construct('P');
}
public function canJump(int $gapLength): bool
{
return $gapLength >= 3 && $gapLength <= 5;
}
}
क्या अंतर हैं ?
- कंस्ट्रक्टर में, मुझे “एम” के बजाय “पी” मिलता है
- कैनजंप विधि की सामग्री अलग है क्योंकि पीच के लिए नियम अलग हैं, यह आकार 3, 4 या 5 के स्थानों पर कूद सकता है।
इसलिए अमूर्त जम्पर वर्ग ने हमें प्रत्येक वर्ग में एक कैनजम्प विधि बनाने के लिए मजबूर किया जो इसे विरासत में मिली थी। लेकिन canJump की सामग्री प्रत्येक वर्ग के लिए विशिष्ट है। उदाहरण के लिए, मेरे पास एक योशी क्लास हो सकती है, एक योशी के साथ जो केवल रिक्त स्थान को छोड़ती है:
public function canJump(int $gapLength): bool
{
return $gapLength % 2 === 0;
}
PHP में एक इंटरफ़ेस लागू करना
हमारी 2 कक्षाएं मारियो और पीच इस बार जम्पर इंटरफ़ेस लागू करेंगी।
इंटरफ़ेस क्या है?
एक इंटरफ़ेस एक “अनुबंध” है जिसे लागू करने वाले वर्ग को इसका पूरी तरह से पालन करना होगा। कहने का तात्पर्य यह है कि इंटरफ़ेस उन तरीकों को इंगित करता है जिन्हें संबंधित कक्षाओं में घोषित किया जाना चाहिए।
यहाँ हमारा इंटरफ़ेस है:
interface Jumper
{
public function canJump(int $gapLength): bool;
public function getName(): string;
}
थोड़ा सा स्पष्टीकरण:
- इसलिए हम इंटरफ़ेस बनाने के लिए कीवर्ड “इंटरफ़ेस” का उपयोग करते हैं! ऐसा होता है कि इंटरफ़ेस में “इंटरफ़ेस” प्रत्यय लगाया जाता है या उसका नाम “…सक्षम” होता है। हम इसे यहाँ “जम्पेबल” कह सकते थे, उदाहरण के लिए => “जम्पेबल”, “जम्पिंग”
- एक इंटरफ़ेस में केवल विधि घोषणाएँ होती हैं। इसलिए यहां हमें कम से कम 2 विधियां बनानी होंगी: canJump (पहले की तरह) और getName, बिना पैरामीटर वाली एक विधि जो एक कैरेक्टर स्ट्रिंग लौटाती है।
अब मारियो वर्ग:
class Mario implements Jumper
{
public function canJump(int $gapLength): bool
{
return $gapLength <= 3;
}
public function getName(): string
{
return 'M';
}
}
थोड़ा सा स्पष्टीकरण:
- CanJump विधि पहले जैसी ही है, रिपोर्ट करने के लिए कुछ भी नहीं है
- हमने $name को प्रबंधित करने वाले कंस्ट्रक्टर को getName विधि से बदल दिया। हम थोड़ी देर बाद इस बिंदु पर लौटेंगे।
यहाँ पीच वर्ग के लिए कोड है:
class Peach implements Jumper
{
public function canJump(int $gapLength): bool
{
return $gapLength >= 3 && $gapLength <= 5;
}
public function getName(): string
{
return 'P';
}
}
नोट करना महत्वपूर्ण है:
- इसलिए पीच और मारियो एक ही इंटरफ़ेस लागू करते हैं, canJump और getName विधियों की 2 कक्षाओं में बिल्कुल समान घोषणा होती है लेकिन उनकी सामग्री अलग-अलग होती है।
हम एक Wario वर्ग की कल्पना कर सकते हैं, एक Wario के साथ जो केवल अभाज्य संख्याओं के अनुरूप रिक्त स्थान को छोड़ सकता है:
class Wario implements Jumper
{
public function canJump(int $gapLength): bool
{
// Code spécifique au comportement de Wario
$possibleGaps = [1, 3, 5, 7, 11, 13, 17];
return in_array($gapLength, $possibleGaps);
}
public function getName(): string
{
return 'W'; // Retour spécifique à Wario
}
}
लेवल वर्ग, 2 कार्यान्वयन के आधार पर अंतर
लेवल क्लास को कई खंडों में विभाजित किया जाएगा:
- स्थिरांक और गुण
- बिल्डर और संपत्ति संवर्धन
- “रन” विधि जो $प्लेटफ़ॉर्म ब्राउज़ करेगी
- प्रतिक्रिया पुनः प्राप्त करने के लिए एक छोटा सा गेटर
यहां वर्ग है (इसके विरासत-संबंधित संस्करण में):
class Level
{
private const PLATFORM = 'P';
private string $sequence="";
private Jumper $currentJumper;
public function __construct(
private string $platfoms,
private Jumper $jumper1,
private Jumper $jumper2
) {
$this->currentJumper = $jumper1;
}
public function run(): void
{
$gaps = explode(self::PLATFORM, $this->platfoms);
foreach ($gaps as $gap) {
$gapLength = strlen($gap);
if ($gapLength === 0) {
// Bords, personne ne saute
continue;
}
// Si les 2 peuvent sauter
if ($this->jumper1->canJump($gapLength) && $this->jumper2->canJump($gapLength)) {
// On incrémente la séquence avec le jumper courant
$this->sequence .= $this->currentJumper->name;
// On change de currentJumper
if ($this->currentJumper->name === $this->jumper1->name) {
$this->currentJumper = $this->jumper2;
} else {
$this->currentJumper = $this->jumper1;
}
continue;
}
if ($this->jumper1->canJump($gapLength)) {
$this->sequence .= $this->jumper1->name;
continue;
}
if ($this->jumper2->canJump($gapLength)) {
$this->sequence .= $this->jumper2->name;
continue;
}
}
}
public function getSequence(): string
{
return $this->sequence;
}
}
“रन” विधि का एक छोटा सा स्पष्टीकरण:
- हम प्रत्येक “अंतराल” को निकालने के लिए “पी” वर्ण के अनुसार $प्लेटफ़ॉर्म का विस्फोट करते हैं
- इनमें से प्रत्येक अंतराल के लिए, हम स्ट्रलेन के साथ लंबाई की गणना करते हैं
- हम यह जांच कर शुरू करते हैं कि क्या दोनों एक ही समय में कूद सकते हैं, और इस मामले में, हम पहले करंट जंपर (वर्तमान जंपर) पर कूदेंगे, फिर हम नाम के आधार पर रिवर्स करेंगे
- जैसे ही हम “अनुक्रम” पूरा कर लेते हैं, हम अगले पुनरावृत्ति पर जाने के लिए “जारी रखें” पर भरोसा करते हैं
- फिर हम जम्पर1 और फिर जम्पर2 की जाँच करते हैं
- ऐसे मामले से शुरुआत करना जरूरी है जहां दोनों एक ही समय में कूद सकें। फिर जंपर1 और जंपर2 पर नियंत्रण के बीच का क्रम कोई मायने नहीं रखता
इंटरफ़ेस-संबंधित संस्करण में, अंतर ->नाम और ->getName() पर है। सबसे पहले, मैंने मारियो और पीच कक्षाओं में एक $name संपत्ति रखी। और सब कुछ बहुत अच्छे से काम किया। लेकिन PHPStan के साथ मेरे स्थैतिक विश्लेषण के दौरान, मुझे निम्नलिखित त्रुटि मिली:
« एक अपरिभाषित संपत्ति तक पहुंच चुनौतियांMARIO_1_interfacesJumper::$name। »
दरअसल, मेरी लेवल क्लास में, जम्पर1 और जंपर2 को “जम्पर” टाइप किया गया है, जिसका अर्थ है कि कोड एक ऐसे वर्ग की अपेक्षा करता है जो इस इंटरफ़ेस को लागू करता है। लेकिन केवल इस कथन से, हम यह अनुमान नहीं लगा सकते कि मारियो और पीच कक्षाओं में $name संपत्ति है, इसलिए यह PHPStan चेतावनी है।
इस अलर्ट को इंटरफ़ेस में यह निर्दिष्ट करके ठीक किया जा सकता है कि PHPDoc का उपयोग करके हमारे पास $name प्रॉपर्टी होगी:
/**
* @property string $name
*/
interface Jumper
{
public function canJump(int $gapLength): bool;
// Et donc plus besoin de getName()
}
लेकिन मुझे यह भयानक नहीं लगा… किसी संपत्ति की घोषणा को “जोड़ना” अजीब है जो शायद वहां नहीं है… मेरे कोड में कुछ भी मुझे ऐसा करने के लिए बाध्य नहीं करता है। एक इंटरफ़ेस में किसी संपत्ति की घोषणा नहीं हो सकती है, इसलिए मैंने इस getName() विधि का उपयोग करना पसंद किया, जो एक गेटर की तरह दिखता है लेकिन यह बिल्कुल एक नहीं है क्योंकि हम सीधे एक कैरेक्टर स्ट्रिंग लौटाते हैं, किसी संपत्ति का मूल्य नहीं।
PHP में इंटरफ़ेस लागू करने और अमूर्त वर्ग का उपयोग करने के बीच चयन कैसे करें?
एकाधिक इंटरफ़ेस
यह ध्यान रखना महत्वपूर्ण है कि एक वर्ग केवल एक अन्य वर्ग से विरासत में प्राप्त कर सकता है। जबकि एक क्लास आवश्यकतानुसार कई इंटरफेस लागू कर सकता है। इसलिए, जब उत्पादित की जाने वाली विधियों के अलग-अलग संदर्भ होंगे तो इंटरफ़ेस को प्राथमिकता दी जाएगी, हम विधियों को समर्पित इंटरफ़ेस में व्यवस्थित कर सकते हैं।
उदाहरण के लिए :
interface Walkable {
public function walk();
}
interface Jumpable {
public function jump();
}
// Mario peut marcher ET voler, on a donc plusieurs interfaces
class Mario implements Walkable, Jumpable {
// Implémentation des méthodes walk() et jump() ici.
}
वंशानुक्रम अधिक चीजों की अनुमति देता है
हमने इसे अपने उदाहरण में कंस्ट्रक्टर और प्रॉपर्टी की पूलिंग के साथ देखा। जहां एक इंटरफ़ेस केवल विधि घोषणाओं की अनुमति देता है, विरासत के साथ मूल वर्ग में अन्य तत्व शामिल हो सकते हैं:
- गुण
- सटीक और व्यापक तरीके
- उत्पादन की विशिष्ट विधियों की घोषणा
उदाहरण के लिए :
abstract class Enemy {
protected int $health;
abstract public function attack();
public function takeDamage(int $amount) {
// Réduire la santé de l'ennemi ici.
$this->health -= $amount; // La même mécanique pour tous les enfants
}
}
class Koopa extends Enemy {
public function attack() {
// Attaque spécifique de Koopa ici.
}
}
class PiranhaPlant extends Enemy {
public function attack() {
// Attaque spécifique de PiranhaPlant ici.
}
}
हम इनहेरिटेंस और इंटरफ़ेस को जोड़ सकते हैं
और हाँ, हमें हमेशा चुनने की ज़रूरत नहीं है! हम वंशानुक्रम (अमूर्त वर्गों के साथ या नहीं) और एक या अधिक इंटरफेस के कार्यान्वयन को जोड़ सकते हैं।
मारियो की दुनिया की प्रसिद्ध उड़ने वाली मछली के साथ एक अंतिम उदाहरण।
यह एक ऐसा शत्रु है जो उड़ भी सकता है और तैर भी सकता है:
// Interfaces
interface Flyable {
public function fly();
}
interface Swimable {
public function swim();
}
// Classe parente
// Cf. exemple précédent
// Classe qui hérite et implémente des interfaces
class FlyingFish extends Enemy implements Flyable, Swimable {
public function attack() {
// Attaque spécifique du FlyingFish.
}
public function fly() {
// Implémentation de la capacité de voler.
}
public function swim() {
// Implémentation de la capacité de nager.
}
}
निष्कर्ष
सही वास्तुकला चुनते समय विचार करने योग्य कुछ तत्व:
- संदर्भ के बारे में सोचो! यदि लागू की जाने वाली विधियाँ उस वर्ग के मूल संदर्भ से असंबंधित हैं जो उन्हें लागू करेगा, और ये विधियाँ कई अलग-अलग कक्षाओं में पाई जा सकती हैं, तो इंटरफ़ेस सही समाधान है।
- इसके विपरीत, यदि संदर्भ जुड़ा हुआ है, और कार्यान्वयन के लिए दोनों तरीके हैं, लेकिन साझा करने के लिए सभी यांत्रिकी से ऊपर, विरासत का उपयोग करने के लिए तार्किक समाधान है।
इस लेख में हमने इस बारे में बात की:
और यह सब किसी और पर परीक्षण करने में संकोच न करें। चुनौती डी कोड 😉
Github पर पूरा कोड:
2023-11-03 12:09:21
#PHP #म #OOP #अमरत #वध #क #सथ #इटरफस #य #वरसत