Introductory Software Engineering with a Focus on Dependency Management Christine Hofmeister.
-
Upload
garry-evans -
Category
Documents
-
view
217 -
download
1
Transcript of Introductory Software Engineering with a Focus on Dependency Management Christine Hofmeister.
Introductory Software Engineering with a Focus on Dependency Management
Christine Hofmeister
Software Engineering Course Constraints
At East Stroudsburg University, the Software Engineering course is completed in the 3rd or 4th year of undergraduate study.
Only one semester course (3 credits): 3 hours of instruction per week 14 weeks
Typical student expertise before this course: writing programs (not systems!) ad-hoc testing.
Why Focus on Dependency Management?
Problem: Students need to learn the challenges of
large-scale development, yet most have had little experience with even small-scale development.
Solution: Focus on design, since that is closest to the
code. Focus on dependency management, since
dependencies are fundamental to design.
Approach
Key techniques for dependency management: Interfaces (for static dependencies) Factory design pattern (for object creation
dependencies). Use UML models to reveal dependencies:
Students learn to abstract key design details into a model.
Models reveal how interfaces and factory patterns remove dependencies.
FlexibleHello1: Creates a Name object, either
with an Encryptor or not. Calls Name’s firstName() and
lastName() methods, and prints the result.
Name: Creates two Message objects: _textin and _textout. Methods firstName() and lastName() do the
following: Use _textout to set and show the prompt. Use _textin to capture the user’s input, which is returned
to the caller.
Hello Example: Version 1
Message: setMessage(): saves the message string (saves
it encrypted, if applicable). showMessage(): displays the current message
string (decrypted, if applicable) on the console. readMessage(): reads a string
from the console and returns it (encrypted, if applicable).(We don’t decrypt in this case simply because we want to see which version of Message was used.)
Hello Example (Version 1 cont.)
FlexibleHello1 knows about all the variants of Message that exist. For the plain text variant, it uses 0 as the Encryptor*. For the secure text variant, it creates an Encryptor.
FlexibleHello1 passes the Encryptor* to Name. Name does not know about the Message variants.
Name simply passes the Encryptor* when it constructs its Message objects.
Message is capable of using both plain text and encrypted messages. It uses the Encryptor* object if available (if it’s non-
null).
V1: FlexibleHello Dependencies
To add a third kind of Message, numerous code changes are required: Modify Message to handle 3 variants instead of
2. Modify main() to pick one of the 3 variants and
pass a flag to Name. Modify Name to pass the variant flag to
Message. Students also see that the Encryptor object
was doing double duty, both as a flag to pick the variant and as the object itself.
Student Lab Exercise: Add LoudMessage (all caps)
Instead of making the Message class handle multiple message variants, create a separate class for each variant.
Hide these message variants behind interface IMessage.
Improvement: Use an Interface
C++ is the language used for this course: It’s a prevalent, mainstream language. It’s a rich language:
Two kinds of variable declarations. No garbage collection. Two kinds of method binding. No interface support.* Methods can exist outside of classes.
These “features” make it an excellent vehicle for teaching polymorphism, interfaces, factory patterns, libraries, and linking.
* Note that sometimes the class declaration in the .h file is called the class’s “interface.” This is not a true interface.
Implementation Language: C++
In C++ we create an interface by using an abstract class with only abstract public operations.
Example of interface in C++:
class IMessage {
public:
virtual void setMessage(std::string) = 0;
virtual void showMessage() = 0;
virtual std::string readMessage() = 0;
virtual ~IMessage() {}
};
Interfaces in C++
Create two separate message classes: Message and SecureMessage. The Encryptor is now encapsulated in SecureMessage.
Provide an interface to the message classes (IMessage).
FlexibleHello2 (main()) chooses which variant of IMessage is used, creates the IMessage object and passes it to Name.
V2: Hello Example with Interfaces
To add a third kind of message, students see that no changes are needed for Name.
However, since Name requires two message objects, FlexibleHello2 must create both and pass both
objects to Name. FlexibleHello2 could create two different
kinds of messages to pass to Name. The fundamental problem is that Name
should create the IMessage objects, but to do so it needs to call the appropriate constructor.
Student Lab Exercise: Add LoudMessage to V2
(a.k.a. Abstract Factory design pattern in Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides. Addison-Wesley 1995.)
Use a class factory when the creator of an object does not know what kind of object to create.
A class factory separates the decision of which object to create from the actual creation of the object.
A class factory is a class that simply creates objects, e.g.:
MessageFactory::MessageFactory(){ }
MessageFactory::~MessageFactory(){ }
IMessage* MessageFactory::newMessage() {
return new Message();
}
Different class factories create different kinds of objects.
Class Factory
Name uses only the interfaces IMessageFactory and IMessage, even though the objects it accesses via these interfaces are really either MessageFactory and Message or SecureMessageFactory and SecureMessage.
V3: Hello Example with Class Factory
main() picks (creates) the Factory.Name uses the IMessageFactory to create an IMessage object (without knowing exactly what type of object it is creating).
To add a third kind of message, students must provide a new class: LoudMessageFactory.
To switch to another kind of message, students must edit FlexibleHello3.cpp to call the
constructor for the appropriate factory rebuild the application.
Student Lab Exercise: Add LoudMessage to V3
Idea: For each interface, provide a C++ method
(not part of a class) that creates an object of the desired class.
Because it’s a C++ method, the linker matches the declaration to the definition it is given.
V4: Hello Example with Factory Method
IMessage* NewMessage();
IMessage* NewMessage(){ return new SecureMessage(); }
IMessage* NewMessage(){ return new Message(); }
Comparison of Factory Patterns
The class factory (V3) is used to create a coherent set of objects for a variant. CatFactory creates PetCat and FoodCat DogFactory creates PetDog and FoodDog
The factory method (V4) is used on the class factory to allow us to select different factories at link time. At runtime, link in one of these:
Cat.lib (contains CatFactory, PetCat, and FoodCat) Dog.lib (contains DogFactory, PetDog, and
FoodDog)
Maximum Flexibility: Combine Both Factory Patterns
Evaluation
Dependency management focus motivated by my prior 7 years in industry: Projects needed interfaces but didn’t have them. Factory design pattern was essential in one family
of systems. Course has evolved over the course of 14
years. Students value the content:
Survey comments Exit interviews Spontaneous feedback
Numeric Course Evaluations
Summary
Dependency management: Interfaces are used to control call-dependencies, and by using C++
(which has no native interface), student learn exactly how an interface differs from a class.
Factories are used to control creation dependencies, by enabling object creation while hiding the type of object being created.
Through their UML modeling work, the students: Learn the UML language. Reinforce their understanding of object-oriented design concepts,
because they are abstracting these aspects of the code. Develop a deeper understanding of C++ language features, e.g.
static vs. dynamic memory allocation, the meaning of the ‘static’ keyword, virtual vs. non-virtual methods, and the meaning of ‘pure virtual’.
Learn to recognize design idioms, e.g. when interfaces and/or factory patterns are used.