SOLID کا استعمال کرتے ہوئے لچکدار کوڈ لکھنا

SOLID کا استعمال کرتے ہوئے لچکدار کوڈ لکھنا

مترجم سے: آپ کے لیے شائع کیا گیا ہے۔ سیورین پیریز کا مضمون پروگرامنگ میں ٹھوس اصولوں کے استعمال کے بارے میں۔ مضمون سے حاصل کردہ معلومات ابتدائی اور تجربہ کار پروگرامرز دونوں کے لیے کارآمد ثابت ہوں گی۔

اگر آپ ترقی میں ہیں، تو آپ نے غالباً ٹھوس اصولوں کے بارے میں سنا ہوگا۔ وہ پروگرامر کو صاف، اچھی ساخت اور آسانی سے برقرار رکھنے کے قابل کوڈ لکھنے کے قابل بناتے ہیں۔ یہ بات قابل غور ہے کہ پروگرامنگ میں کسی خاص کام کو صحیح طریقے سے انجام دینے کے کئی طریقے ہیں۔ مختلف ماہرین کے "صحیح راستے" کے بارے میں مختلف خیالات اور تفہیم ہوتے ہیں؛ یہ سب ہر شخص کے تجربے پر منحصر ہے۔ تاہم، SOLID میں اعلان کردہ خیالات کو IT کمیونٹی کے تقریباً تمام نمائندوں نے قبول کیا ہے۔ وہ بہت سے اچھے ترقیاتی انتظامی طریقوں کے ظہور اور ترقی کا نقطہ آغاز بن گئے۔

آئیے سمجھتے ہیں کہ ٹھوس اصول کیا ہیں اور وہ ہماری مدد کیسے کرتے ہیں۔

Skillbox تجویز کرتا ہے: پریکٹیکل کورس "موبائل ڈویلپر پی آر او".

ہم آپ کو یاد دلاتے ہیں: "Habr" کے تمام قارئین کے لیے - "Habr" پروموشنل کوڈ کا استعمال کرتے ہوئے کسی بھی Skillbox کورس میں داخلہ لینے پر 10 rubles کی رعایت۔

SOLID کیا ہے؟

یہ اصطلاح ایک مخفف ہے، اصطلاح کا ہر حرف ایک خاص اصول کے نام کا آغاز ہے:

  • Sواحد ذمہ داری کا اصول۔ ایک ماڈیول میں تبدیلی کی ایک اور صرف ایک وجہ ہو سکتی ہے۔
  • ۔ Oقلم/بند اصول (کھلے/بند اصول)۔ کلاسز اور دیگر عناصر کو توسیع کے لیے کھلا ہونا چاہیے، لیکن ترمیم کے لیے بند ہونا چاہیے۔
  •  ۔ Liskov متبادل اصول (لیسکوف متبادل اصول)۔ بنیادی قسم کا استعمال کرنے والے فنکشنز کو بغیر جانے جانے کے بنیادی قسم کے ذیلی قسموں کو استعمال کرنے کے قابل ہونا چاہئے۔
  • ۔ Iانٹرفیس علیحدگی کا اصول  (انٹرفیس علیحدگی کا اصول)۔ سافٹ ویئر اداروں کو ان طریقوں پر انحصار نہیں کرنا چاہئے جو وہ استعمال نہیں کرتے ہیں۔
  • ۔ Dانحصار الٹا اصول (انحصار الٹنے کا اصول)۔ اعلی سطح پر ماڈیولز کو نچلی سطح پر ماڈیولز پر انحصار نہیں کرنا چاہیے۔

واحد ذمہ داری کا اصول


واحد ذمہ داری کا اصول (SRP) کہتا ہے کہ کسی پروگرام میں ہر کلاس یا ماڈیول کو اس پروگرام کی فعالیت کے صرف ایک حصے کے لیے ذمہ دار ہونا چاہیے۔ مزید برآں، اس ذمہ داری کے عناصر کو ان کی اپنی کلاس کو تفویض کیا جانا چاہئے، بجائے اس کے کہ غیر متعلقہ طبقات میں بکھر جائیں۔ ایس آر پی کے ڈویلپر اور چیف مبشر، رابرٹ ایس مارٹن، تبدیلی کی وجہ احتساب کو بیان کرتے ہیں۔ اس نے اصل میں اس اصطلاح کو اپنے کام "آبجیکٹ اورینٹڈ ڈیزائن کے اصول" کے عناصر میں سے ایک کے طور پر تجویز کیا تھا۔ اس تصور میں زیادہ تر کنیکٹیویٹی پیٹرن شامل ہے جس کی وضاحت پہلے ٹام ڈی مارکو نے کی تھی۔

اس تصور میں ڈیوڈ پارناس کے وضع کردہ کئی تصورات بھی شامل تھے۔ دو اہم ہیں encapsulation اور معلومات کو چھپانا۔ پرناس نے دلیل دی کہ کسی نظام کو الگ الگ ماڈیولز میں تقسیم کرنا بلاک ڈایاگرام یا عمل درآمد کے بہاؤ کے تجزیہ پر مبنی نہیں ہونا چاہیے۔ کسی بھی ماڈیول میں ایک مخصوص حل ہونا چاہیے جو کلائنٹس کو کم سے کم معلومات فراہم کرے۔

ویسے، مارٹن نے ایک کمپنی کے سینئر مینیجرز (COO، CTO، CFO) کے ساتھ ایک دلچسپ مثال دی، جن میں سے ہر ایک مختلف مقاصد کے لیے مخصوص کاروباری سافٹ ویئر استعمال کرتا ہے۔ نتیجے کے طور پر، ان میں سے کوئی بھی دوسرے مینیجرز کے مفادات کو متاثر کیے بغیر سافٹ ویئر میں تبدیلیاں لاگو کر سکتا ہے۔

خدائی چیز

ہمیشہ کی طرح، SRP سیکھنے کا بہترین طریقہ یہ ہے کہ اسے عملی طور پر دیکھیں۔ آئیے پروگرام کے ایک حصے کو دیکھتے ہیں جو واحد ذمہ داری کے اصول پر عمل نہیں کرتا ہے۔ یہ روبی کوڈ ہے جو خلائی اسٹیشن کے رویے اور صفات کو بیان کرتا ہے۔

مثال کا جائزہ لیں اور درج ذیل کا تعین کرنے کی کوشش کریں:
ان اشیاء کی ذمہ داریاں جنہیں SpaceStation کلاس میں قرار دیا گیا ہے۔
وہ لوگ جو خلائی اسٹیشن کے آپریشن میں دلچسپی لے سکتے ہیں۔

class SpaceStation
  def initialize
    @supplies = {}
    @fuel = 0
  end
 
  def run_sensors
    puts "----- Sensor Action -----"
    puts "Running sensors!"
  end
 
  def load_supplies(type, quantity)
    puts "----- Supply Action -----"
    puts "Loading #{quantity} units of #{type} in the supply hold."
    
    if @supplies[type]
      @supplies[type] += quantity
    else
      @supplies[type] = quantity
    end
  end
 
  def use_supplies(type, quantity)
    puts "----- Supply Action -----"
    if @supplies[type] != nil && @supplies[type] > quantity
      puts "Using #{quantity} of #{type} from the supply hold."
      @supplies[type] -= quantity
    else
      puts "Supply Error: Insufficient #{type} in the supply hold."
    end
  end
 
  def report_supplies
    puts "----- Supply Report -----"
    if @supplies.keys.length > 0
      @supplies.each do |type, quantity|
        puts "#{type} avalilable: #{quantity} units"
      end
    else
      puts "Supply hold is empty."
    end
  end
 
  def load_fuel(quantity)
    puts "----- Fuel Action -----"
    puts "Loading #{quantity} units of fuel in the tank."
    @fuel += quantity
  end
 
  def report_fuel
    puts "----- Fuel Report -----"
    puts "#{@fuel} units of fuel available."
  end
 
  def activate_thrusters
    puts "----- Thruster Action -----"
    if @fuel >= 10
      puts "Thrusting action successful."
      @fuel -= 10
    else
      puts "Thruster Error: Insufficient fuel available."
    end
  end
end

دراصل، ہمارا خلائی اسٹیشن غیر فعال ہے (مجھے نہیں لگتا کہ مجھے جلد ہی کسی بھی وقت NASA سے کال موصول ہوگی)، لیکن یہاں تجزیہ کرنے کے لیے کچھ ہے۔

اس طرح، SpaceStation کلاس میں کئی مختلف ذمہ داریاں (یا کام) ہیں۔ ان سب کو اقسام میں تقسیم کیا جا سکتا ہے:

  • سینسر؛
  • سامان (استعمال کی اشیاء)؛
  • ایندھن
  • ایکسلریٹر

اگرچہ سٹیشن کے ملازمین میں سے کسی کو بھی کلاس تفویض نہیں کیا گیا ہے، ہم آسانی سے تصور کر سکتے ہیں کہ کون کس چیز کا ذمہ دار ہے۔ زیادہ تر امکان ہے کہ، سائنس دان سینسرز کو کنٹرول کرتا ہے، رسد فراہم کرنے والا وسائل کی فراہمی کا ذمہ دار ہوتا ہے، انجینئر ایندھن کی فراہمی کا ذمہ دار ہوتا ہے، اور پائلٹ بوسٹرز کو کنٹرول کرتا ہے۔

کیا ہم کہہ سکتے ہیں کہ یہ پروگرام SRP کے مطابق نہیں ہے؟ ہاں بالکل. لیکن SpaceStation کلاس ایک عام "خدا آبجیکٹ" ہے جو سب کچھ جانتا ہے اور سب کچھ کرتا ہے۔ آبجیکٹ اورینٹڈ پروگرامنگ میں یہ ایک بڑا اینٹی پیٹرن ہے۔ ایک ابتدائی کے لیے، ایسی اشیاء کو برقرار رکھنا انتہائی مشکل ہے۔ اب تک کا پروگرام بہت آسان ہے، ہاں، لیکن تصور کریں کہ اگر ہم نئی خصوصیات شامل کریں گے تو کیا ہوگا۔ شاید ہمارے خلائی اسٹیشن کو میڈیکل اسٹیشن یا میٹنگ روم کی ضرورت ہوگی۔ اور جتنے زیادہ فنکشنز ہوں گے، اتنا ہی اسپیس اسٹیشن بڑھے گا۔ ٹھیک ہے، چونکہ یہ سہولت دوسروں سے منسلک ہو جائے گی، اس لیے پورے کمپلیکس کی سروسنگ اور بھی پیچیدہ ہو جائے گی۔ نتیجے کے طور پر، ہم مثال کے طور پر، ایکسلریٹر کے آپریشن میں خلل ڈال سکتے ہیں۔ اگر کوئی محقق سینسر میں تبدیلی کی درخواست کرتا ہے، تو یہ اسٹیشن کے مواصلاتی نظام کو بہت اچھی طرح سے متاثر کر سکتا ہے۔

ایس آر پی کے اصول کی خلاف ورزی ایک قلیل مدتی حکمت عملی سے فتح حاصل کر سکتی ہے، لیکن آخر کار ہم "جنگ ہار جائیں گے"، اور مستقبل میں ایسے عفریت کو برقرار رکھنا بہت مشکل ہو جائے گا۔ پروگرام کو کوڈ کے الگ الگ حصوں میں تقسیم کرنا بہتر ہے، جن میں سے ہر ایک مخصوص آپریشن کرنے کا ذمہ دار ہے۔ اس کو سمجھتے ہوئے، آئیے SpaceStation کلاس کو تبدیل کرتے ہیں۔

آئیے ذمہ داریاں تقسیم کریں۔

اوپر ہم نے چار قسم کے آپریشنز کی وضاحت کی ہے جو SpaceStation کلاس کے ذریعے کنٹرول ہوتے ہیں۔ ری فیکٹرنگ کرتے وقت ہم انہیں ذہن میں رکھیں گے۔ اپ ڈیٹ شدہ کوڈ SRP سے بہتر میل کھاتا ہے۔

class SpaceStation
  attr_reader :sensors, :supply_hold, :fuel_tank, :thrusters
 
  def initialize
    @supply_hold = SupplyHold.new
    @sensors = Sensors.new
    @fuel_tank = FuelTank.new
    @thrusters = Thrusters.new(@fuel_tank)
  end
end
 
class Sensors
  def run_sensors
    puts "----- Sensor Action -----"
    puts "Running sensors!"
  end
end
 
class SupplyHold
  attr_accessor :supplies
 
  def initialize
    @supplies = {}
  end
 
  def load_supplies(type, quantity)
    puts "----- Supply Action -----"
    puts "Loading #{quantity} units of #{type} in the supply hold."
    
    if @supplies[type]
      @supplies[type] += quantity
    else
      @supplies[type] = quantity
    end
  end
 
  def use_supplies(type, quantity)
    puts "----- Supply Action -----"
    if @supplies[type] != nil && @supplies[type] > quantity
      puts "Using #{quantity} of #{type} from the supply hold."
      @supplies[type] -= quantity
    else
      puts "Supply Error: Insufficient #{type} in the supply hold."
    end
  end
 
  def report_supplies
    puts "----- Supply Report -----"
    if @supplies.keys.length > 0
      @supplies.each do |type, quantity|
        puts "#{type} avalilable: #{quantity} units"
      end
    else
      puts "Supply hold is empty."
    end
  end
end
 
class FuelTank
  attr_accessor :fuel
 
  def initialize
    @fuel = 0
  end
 
  def get_fuel_levels
    @fuel
  end
 
  def load_fuel(quantity)
    puts "----- Fuel Action -----"
    puts "Loading #{quantity} units of fuel in the tank."
    @fuel += quantity
  end
 
  def use_fuel(quantity)
    puts "----- Fuel Action -----"
    puts "Using #{quantity} units of fuel from the tank."
    @fuel -= quantity
  end
 
  def report_fuel
    puts "----- Fuel Report -----"
    puts "#{@fuel} units of fuel available."
  end
end
 
class Thrusters
  def initialize(fuel_tank)
    @linked_fuel_tank = fuel_tank
  end
 
  def activate_thrusters
    puts "----- Thruster Action -----"
    if @linked_fuel_tank.get_fuel_levels >= 10
      puts "Thrusting action successful."
      @linked_fuel_tank.use_fuel(10)
    else
      puts "Thruster Error: Insufficient fuel available."
    end
  end
end

بہت سی تبدیلیاں ہیں، پروگرام یقیناً اب بہتر نظر آ رہا ہے۔ اب ہماری اسپیس سٹیشن کلاس ایک کنٹینر بن گئی ہے جس میں منحصر حصوں کے لیے آپریشن شروع کیے جاتے ہیں، بشمول سینسر کا ایک سیٹ، ایک قابل استعمال سپلائی سسٹم، ایک ایندھن کا ٹینک، اور بوسٹر۔

کسی بھی متغیر کے لیے اب ایک متعلقہ کلاس ہے: سینسر؛ سپلائی ہولڈ؛ ایندھن کے ٹینک؛ تھرسٹرس۔

کوڈ کے اس ورژن میں کئی اہم تبدیلیاں ہیں۔ نکتہ یہ ہے کہ انفرادی افعال نہ صرف ان کی اپنی کلاسوں میں سمیٹے جاتے ہیں، بلکہ وہ اس طرح سے منظم ہوتے ہیں کہ پیش گوئی کے قابل اور مستقل ہو جائیں۔ ہم آہنگی کے اصول پر عمل کرنے کے لیے ہم عناصر کو ایک جیسی فعالیت کے ساتھ گروپ کرتے ہیں۔ اب، اگر ہمیں نظام کے کام کرنے کے طریقے کو تبدیل کرنے کی ضرورت ہے، ہیش سٹرکچر سے ایک صف میں منتقل ہو، تو صرف سپلائی ہولڈ کلاس استعمال کریں؛ ہمیں دوسرے ماڈیولز کو چھونے کی ضرورت نہیں ہے۔ اس طرح اگر لاجسٹکس آفیسر اپنے سیکشن میں کچھ تبدیل کرتا ہے تو باقی اسٹیشن برقرار رہے گا۔ اس صورت میں، SpaceStation کلاس کو تبدیلیوں کا علم بھی نہیں ہوگا۔

خلائی اسٹیشن پر کام کرنے والے ہمارے افسران شاید تبدیلیوں سے خوش ہیں کیونکہ وہ اپنی ضرورت کی درخواست کر سکتے ہیں۔ نوٹ کریں کہ کوڈ میں سپلائی ہولڈ اور فیول ٹینک کی کلاسوں میں رپورٹ_سپلائیز اور رپورٹ_فیول جیسے طریقے ہیں۔ کیا ہوگا اگر زمین اپنے رپورٹ کرنے کے طریقے کو تبدیل کرنے کو کہے؟ دونوں کلاسز، سپلائی ہولڈ اور فیول ٹینک کو تبدیل کرنے کی ضرورت ہوگی۔ اگر آپ کو ایندھن اور استعمال کی اشیاء کی ترسیل کے طریقے کو تبدیل کرنے کی ضرورت ہو تو کیا ہوگا؟ آپ کو شاید ایک ہی کلاس کو دوبارہ تبدیل کرنا پڑے گا۔ اور یہ پہلے ہی SRP اصول کی خلاف ورزی ہے۔ آئیے اسے ٹھیک کریں۔

class SpaceStation
  attr_reader :sensors, :supply_hold, :supply_reporter,
              :fuel_tank, :fuel_reporter, :thrusters
 
  def initialize
    @sensors = Sensors.new
    @supply_hold = SupplyHold.new
    @supply_reporter = SupplyReporter.new(@supply_hold)
    @fuel_tank = FuelTank.new
    @fuel_reporter = FuelReporter.new(@fuel_tank)
    @thrusters = Thrusters.new(@fuel_tank)
  end
end
 
class Sensors
  def run_sensors
    puts "----- Sensor Action -----"
    puts "Running sensors!"
  end
end
 
class SupplyHold
  attr_accessor :supplies
  attr_reader :reporter
 
  def initialize
    @supplies = {}
  end
 
  def get_supplies
    @supplies
  end
 
  def load_supplies(type, quantity)
    puts "----- Supply Action -----"
    puts "Loading #{quantity} units of #{type} in the supply hold."
    
    if @supplies[type]
      @supplies[type] += quantity
    else
      @supplies[type] = quantity
    end
  end
 
  def use_supplies(type, quantity)
    puts "----- Supply Action -----"
    if @supplies[type] != nil && @supplies[type] > quantity
      puts "Using #{quantity} of #{type} from the supply hold."
      @supplies[type] -= quantity
    else
      puts "Supply Error: Insufficient #{type} in the supply hold."
    end
  end
end
 
class FuelTank
  attr_accessor :fuel
  attr_reader :reporter
 
  def initialize
    @fuel = 0
  end
 
  def get_fuel_levels
    @fuel
  end
 
  def load_fuel(quantity)
    puts "----- Fuel Action -----"
    puts "Loading #{quantity} units of fuel in the tank."
    @fuel += quantity
  end
 
  def use_fuel(quantity)
    puts "----- Fuel Action -----"
    puts "Using #{quantity} units of fuel from the tank."
    @fuel -= quantity
  end
end
 
class Thrusters
  FUEL_PER_THRUST = 10
 
  def initialize(fuel_tank)
    @linked_fuel_tank = fuel_tank
  end
 
  def activate_thrusters
    puts "----- Thruster Action -----"
    
    if @linked_fuel_tank.get_fuel_levels >= FUEL_PER_THRUST
      puts "Thrusting action successful."
      @linked_fuel_tank.use_fuel(FUEL_PER_THRUST)
    else
      puts "Thruster Error: Insufficient fuel available."
    end
  end
end
 
class Reporter
  def initialize(item, type)
    @linked_item = item
    @type = type
  end
 
  def report
    puts "----- #{@type.capitalize} Report -----"
  end
end
 
class FuelReporter < Reporter
  def initialize(item)
    super(item, "fuel")
  end
 
  def report
    super
    puts "#{@linked_item.get_fuel_levels} units of fuel available."
  end
end
 
class SupplyReporter < Reporter
  def initialize(item)
    super(item, "supply")
  end
 
  def report
    super
    if @linked_item.get_supplies.keys.length > 0
      @linked_item.get_supplies.each do |type, quantity|
        puts "#{type} avalilable: #{quantity} units"
      end
    else
      puts "Supply hold is empty."
    end
  end
end
 
iss = SpaceStation.new
 
iss.sensors.run_sensors
  # ----- Sensor Action -----
  # Running sensors!
 
iss.supply_hold.use_supplies("parts", 2)
  # ----- Supply Action -----
  # Supply Error: Insufficient parts in the supply hold.
iss.supply_hold.load_supplies("parts", 10)
  # ----- Supply Action -----
  # Loading 10 units of parts in the supply hold.
iss.supply_hold.use_supplies("parts", 2)
  # ----- Supply Action -----
  # Using 2 of parts from the supply hold.
iss.supply_reporter.report
  # ----- Supply Report -----
  # parts avalilable: 8 units
 
iss.thrusters.activate_thrusters
  # ----- Thruster Action -----
  # Thruster Error: Insufficient fuel available.
iss.fuel_tank.load_fuel(100)
  # ----- Fuel Action -----
  # Loading 100 units of fuel in the tank.
iss.thrusters.activate_thrusters
  # ----- Thruster Action -----
  # Thrusting action successful.
  # ----- Fuel Action -----
  # Using 10 units of fuel from the tank.
iss.fuel_reporter.report
  # ----- Fuel Report -----
# 90 units of fuel available.

پروگرام کے اس تازہ ترین ورژن میں، ذمہ داریوں کو دو نئی کلاسوں، FuelReporter اور SupplyReporter میں تقسیم کیا گیا ہے۔ یہ دونوں رپورٹر کلاس کے بچے ہیں۔ اس کے علاوہ، ہم نے SpaceStation کلاس میں مثال کے متغیرات کو شامل کیا تاکہ اگر ضروری ہو تو مطلوبہ ذیلی طبقے کو شروع کیا جا سکے۔ اب، اگر زمین کچھ اور تبدیل کرنے کا فیصلہ کرتی ہے، تو ہم ذیلی طبقات میں تبدیلیاں کریں گے، نہ کہ مرکزی طبقے میں۔

یقینا، ہماری کچھ کلاسیں اب بھی ایک دوسرے پر منحصر ہیں۔ اس طرح، SupplyReporter آبجیکٹ کا انحصار SupplyHold پر ہے، اور FuelReporter FuelTank پر منحصر ہے۔ بلاشبہ، بوسٹرز کو فیول ٹینک سے جوڑا جانا چاہیے۔ لیکن یہاں سب کچھ پہلے سے ہی منطقی نظر آتا ہے، اور تبدیلیاں کرنا خاص طور پر مشکل نہیں ہوگا - ایک چیز کے کوڈ میں ترمیم کرنے سے دوسری چیز کو زیادہ متاثر نہیں کیا جائے گا۔

اس طرح، ہم نے ایک ماڈیولر کوڈ بنایا ہے جہاں ہر ایک آبجیکٹ/کلاسز کی ذمہ داریوں کو بالکل واضح طور پر بیان کیا گیا ہے۔ ایسے کوڈ کے ساتھ کام کرنا کوئی مسئلہ نہیں ہے، اسے برقرار رکھنا ایک آسان کام ہوگا۔ ہم نے پوری "خدائی چیز" کو SRP میں تبدیل کر دیا ہے۔

Skillbox تجویز کرتا ہے:

ماخذ: www.habr.com

نیا تبصرہ شامل کریں