June 7, 2021

C# - הזרקת תלויות בצורה נכונה

אם אתם מוצאים את עצמם כותבים את הקוד הזה, הגיע הזמן שתלמדו איך לכתוב נכון!

מה זה להזריק תלויות?

הדוגמאות האהובות עלי הן מידול של מוצרים אלקטרוניים לתוכנה.

מה זה תלות?

תלות היא משהו שיחידת עבודה צריכה כדי לעבוד.
למשל עבור בלנדר צריך סכין, אחרת הבלנדר לא ייעשה כלום.

ומעולם התוכנה:

  • שכבת מידע שצריכה פונקציונליות מסד נתונים.
  • שכבת תצוגה שצריכה להציג את המידע על חלון כלשהו.

מה זה אומר להזריק תלות?

להכניס יחידת עבודה או מידע לתוך יחידת עבודה אחרת.

הבלנדר הוא דוגמא מעולה:

הוא נותן לנו אופציה להכניס סכין לתוך שקע - וסוג הסכין קובע איזו פונקציונליות הבלנדר מבצע.

דוגמאות קוד

אני אשתמש בממשקים ב-C# כדי לבצע הזרקת תלויות.
וזה מתבצע בעזרת מאפיינים (Property).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ICutter
{
IEnumerable<TCut> Cut<T, TCut>(T toCut);
}

public class Blender
{
public ICutter CurrentCutter { get; set; }

public void Blend<T,TPiece>(T toBlend)
{
var pieces = CurrentCutter?.Cut<T, TPiece>(toBlend);
/// לעשות משהו עם החתיכות
}
}

סוגים של הזרקת תלויות בקוד

Types of Code Injections

יש 2 סוגים עיקריים:

  • דרך הבנאי

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Blender
    {
    private ICutter mCutter;

    public Blender(ICutter cutter)
    {
    mCutter = cutter;
    }
    }
  • מתודה או מאפיין

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Blender
    {
    private ICutter mCutter;

    public void SetCutter(ICutter cutter)
    {
    mCutter = cutter;
    }

    // או
    public ICutter CurrentCutter { get; set; }
    }

הסוגים האלו תומכים גם בתבנית האסטרטגיה.
ניתן לקרוא על זה כאן:

Strategy pattern

ניתוק רכיבים

היתרון העיקרי של הזרקת תלויות הוא הניתוק בין רכיבי עבודה שתלויים אחד בשני.
למשל בדוגמא שלנו הסכין של הבלנדר לא מובנה בתוך הבלנדר.
מה שמאפשר לנו להחליף אותו, לנקות, לתחזק ולתקן את הסכינים בצורה קלה יותר.
בדיוק כמו בקוד!!

יחידות מודולריות הרבה יותר קלות לתחזק ולתקן - אם הסכין לא חותכת כמו שצריך אנחנו יודעים איפה הבעיה, ולא נתחיל לחפש במנוע של הבלנדר.

כמו שכתבתי בתבנית האסטרטגיה:
כאן
זה גם הרבה יותר קל לבדוק יחידות קטנות יותר.

מכל תלויות - Dependency Containers

קונספט מאוד חשוב הם המכלים - Containers.
אני אגיד “קונטיינר” במקום מיכל כי זה קליט יותר.
אנחנו נמנעים מהלשתמש ידנית במילה - new.

מה זה קונטיינר תלויות

הקונטיינר אחראי על הכלה של תבניות שאומרות איך ליצור רכיבים.
והעבודה שלו זה ליצור מופעים של מחלקות - הרכיבים עצמם.
או בC# אנחנו שומרים Typeים ויוצרים מופעים.

הממשק של קונטיינר

אני כבר מימשתי כמה קונטיינרים עם רישום ומעגל החיים של הרכיבים.
הנה איך ממשק בד”כ נראה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum InstanceLifecycle
{
Instance,
Singelton,
Pooled
}

public interface IDIContainer
{
IDIContainer Register<T>(InstanceLifecycle lifecycle);
IDIContainer Register(Type type, InstanceLifecycle lifecycle);

T Create<T>();
object Create(Type type);
}

מעגל החיים של תלות:

  • רישום - איך ליצור מופע מסוג T כמו כן איזה סוג של תלויות יש לו.
  • יצירת מופע
  • מחיקת מופע

ImageSaver מכיל ICompressor ו IStore.
המופע של IStore תלוי ב-IFileIO.

הרישום בד”כ עובד על הרמה העיקרית של האבסטרקציה של המופע - בד”כ ממשק כלשהו.

1
Container.Register<IStore, FileStore>();

Then a resolve is made:

1
2
// הוא לא יודע איזה סוג של מופע זה - רק הממשק!  
var store = Container.Resolve<IStore>();

הקונטיינר יכול לממש את זה בצורות שונות.
הוא יכול להיות סינגלטון, מימוש pool או מופעים חדשים.

מופעים ברישום ע”י שמות

What if we have 2 implementations?

1
2
3
4
5
6
7
8
9
IDIContainer RegisterNamed<TInstance,TImp>(string name);
TInstance Resolve<TInstance>(string name);

// ...
RegisterNamed<IStore, FileStore>("ProductsStore");
RegisterNamed<IStore, FileStore>("ClientsStore");

var productsStore = ResolveNamed<IStore>("ProductsStore");
var clientsStore = ResolveNamed<IStore>("ClientsStore");

רישום ע”פ שמות יכול לפתור לנו את הבעיה הזו.
ניתן ליצור קוד יותר מורכב ע”י תוכנות (Attributes) או מטה-דטה נוסף.
לא ניכנס לזה פה.

קונטיינרים בטבע

הנה 3 דוגמאות לקונטיינרים מפורסמים ב-C#.

StructureMap

Ninject

Unity

קונטיינרים של תלויות מצילות לנו את החיים.

בסופו של דבר שימוש בקונטיינרים נותן לנו הרבה כוח על איך מופעים נוצרים.

הפעם היחידה שנשתמש אולי במילה - new זה כדי ליצור את הקונטיינר עצמו.
יש קונטיינרים חכמים שיודעים ליצור מופע מבלי לדעת איך רשמו אותו.

1
2
3
4
5
6
7
8
9
10
11
public void Main()
{
Container cotainer = new Container();
container.Register<IStore,FileStore>();
container.Register<INetworkParser, NetworkParser>();
container.Register<ICompressor, JpegCompressor>();

// StorageServer isn't even registered!
var server = container.Resolve<StorageServer>();
server.Run();
}

זה נותן לנו גישה למעגל החיים של הרכיבים ויצירת רכיבים מודולריות - אין כמו מודולריות.

תודה על הקריאה!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#C##Software