Composition9:07 with Jeremy McLain
Using class composition and interfaces together make for a very useful pattern (common programming technique).
We learned earlier that inheritance allows us to create 0:00 a new type that includes all of the functionality of another class. 0:03 We can do something similar using composition. 0:07 The idea of composition is to create a new type by combining one or 0:10 more other types together. 0:14 Composition is very common in object-oriented programming. 0:15 The level class, for example, is composed of many IInvader objects and 0:19 many Tower objects. 0:24 Objects can be passed in at construction time, or created any time during or 0:25 after the object's creation. 0:30 Let's use composition to create a new type of invader that behaves like an invader, 0:32 but doesn't directly inherit from the invader base class. 0:37 We can accomplish this by implementing the IInvader interface. 0:40 We'll call this new type of invader a resurrecting invader. 0:45 So I'll create a new file and 0:48 name it ResurrectingInvader.cs. 0:52 And in the namespace TreehouseDefense We'll 0:56 create a new class ResurrectingInvader, 1:04 and it will implement the IInvader interface. 1:09 So this invader, once it's destroyed, will resurrect into a stronger, 1:14 more fearsome invader. 1:19 We'll have its first incarnation be a basic invader, and 1:21 we'll make that a private field. 1:24 So I'll say private BasicInvader, and I'll name this _incarnation1;. 1:25 And the second incarnation will be a StrongInvader. 1:34 Just like other types of invaders, 1:44 it will also take a path parameter in its constructor. 1:46 But it won't forward this to the base class, 1:50 because there is actually no base class to this ResurrectingInvader. 1:52 Remember, IInvader isn't a class, it's an interface. 1:56 Instead, in the constructor we'll create the two invaders that compose our class. 2:01 So we'll say _incarnation1 = new BasicInvader and pass it the path. 2:07 And we'll do the same thing with _incarnation2, 2:18 except it's a StrongInvader. 2:21 There we go. 2:26 Because ResurrectingInvader implements the IInvader interface, we must implement all 2:27 of the members of the interface in ResurrectingInvader as well. 2:32 So we'll go to the IInvader interface and we'll copy the member declarations, 2:36 And we'll paste them here. 2:45 IInvader inherits from IMovable and IMappable, so 2:50 we'll also need to copy the Move method and the Location property. 2:54 You may have noticed that I like to put my properties above the constructor and 3:08 I put my methods below it. 3:13 All members in an interface must be public, so 3:15 we need to make them all public now. 3:18 Now it's just a matter of implementing each of these members one by one. 3:29 The Location will be the location of the second incarnation, 3:33 if the first incarnation has been neutralized. 3:36 Otherwise, it will be the location of the first incarnation. 3:39 We can use a ternary if statement to put this all in one line. 3:42 So we'll say Location => 3:45 _incarnation1.Is neutralized. 3:49 So if it's neutralized, it will return _incarnation 3:57 2.location, otherwise, 4:04 it'll return _incarnation1.location. 4:08 HasScored will return true if either incarnation has scored. 4:18 So we'll say HasScored returns 4:23 _incarnation1.HasScored or 4:28 _incarnation2.HasScored. 4:33 Health will return the health of the non neutralized incarnation, 4:38 just like we did with Location. 4:42 We can just copy this from Location up here, And 4:44 we'll say so if _incarnation1 is neutralized, 4:50 then we'll return _incarnation2's Health. 4:55 Otherwise, we'll return _incarnation1's Health. 4:59 IsNeutralized will return true if both of the incarnations have been neutralized. 5:05 And IsActive will return true if the invader is neither neutralized, 5:19 nor has it scored. 5:24 When the Move method is called, both incarnations will be moved down the path. 5:28 This way, if _incarnation1 is neutralized, 5:33 then _incarnation2 will just pick up where _incarnation1 left off. 5:36 DecreaseHealth will decrease the health of _incarnation1 if it hasn't been 5:41 neutralized yet. 5:45 Otherwise, the health of _incarnation2 will be decreased. 5:46 We'll say if !_incarnation1.IsNeutralized. 5:51 So if _incarnation1 is not neutralized, 5:58 Then _incarnation1.DecreaseHealth. 6:03 And we'll pass it the factor. 6:10 Otherwise, _incarnation2.DecreaseHealth. 6:15 So it could have written the code for this class half a dozen different ways. 6:26 For example, 6:30 we could have set _incarnation1 to null when it was neutralized, or we could have 6:31 had a third IInvader variable to keep track of which incarnation was alive. 6:35 For the users of this class, 6:40 all that matters is that the interface is implemented. 6:42 As you can see, abstraction and encapsulation go hand in hand. 6:45 By relying on interfaces, 6:49 we're given the most freedom possible to implement the class as we see fit. 6:51 Notice that we aren't inheriting from the IInvader base class, 6:55 we're using composition instead. 6:59 This is a good example of where we can use composition instead of inheritance. 7:01 In fact, most developers prefer to use composition and 7:06 implement the interface, instead of inheriting from a base class. 7:10 This gives us the most flexibility to alter the class in the future, and 7:14 it removes the dependency on the base class. 7:18 In general, we want to minimize the amount of code that our code is dependent on. 7:21 Interfaces allow us to be dependent on the interface, 7:25 instead of the concrete implementation of classes. 7:29 That's the beauty of abstraction at work. 7:32 Let's add a resurrecting invader to one of the invaders in our level. 7:35 This is becoming quite a difficult level with all these advanced types of invaders. 7:49 We've also got some code here for 7:54 testing the IsOnPath method that we can remove now. 7:55 Let's compile to make sure nothing broke. 8:01 Hm, we've gotten a few compilation errors. 8:09 Let's see if we can resolve them. 8:11 Let's go back to ResurrectingInvader here and 8:13 line 16, we forgot our =>. 8:18 Compile again, hmm we've got another compilation error here. 8:22 This says cannot implicitly convert type 8:28 'TreehouseDefense.ResurrectingInvader' to 'TreehouseDefense.Invader'. 8:30 Hmm, I think I know what's going on here. 8:36 Over here in the game class, 8:39 ResurrectingInvader is not a type of invader. 8:41 Remember, it doesn't inherit from invader, it implements the IInvader interface. 8:46 Because each of these other invaders also implements the IInvader interface 8:53 through the invader class, we can just change this to IInvader. 8:57 Looks like that fixed it. 9:05
You need to sign up for Treehouse in order to download course files.Sign up