PowerShell and Design Patterns
Writing good PowerShell scripts is hard. Writing reusable PowerShell scripts, is even harder. This is re-phrasing a line from the book “Design Patterns Elements of Reusable Object-Oriented Software”.
Designing object-oriented software is hard, and designing reusable object-oriented software is even harder.
The book is an excellent guide on how to think about what you’re building.
Benefits
How many times have you written PowerShell scripts only to re-write them when you need to do something a bit different? Or worse, hacked up what you’ve done, make it work but you have a set of scripts that are very fragile and brittle. They start to break more with each change an addition.
Design Patterns help you fix that.
When you hand over your PowerShell scripts to others, a co-worker, in your open source projects and you go to explain how parts of these scirpts work, how painful is that? Is there an easy explanation?
Design Patterns help you fix that.
Are desgin patterns worth learning? I suggest they are. The really good news is, they apply to pretty much any software language. Go Google any of the following:
- design patterns C#
- design patterns Ruby
- design patterns Python
- design patterns JavaScript
- design patterns Groovy
- design patterns Rust
And, it doesn’t stop there. Plus, you’ll find in other languages there are design patterns created that ares not in the original book, because each language is tuned to do different things. There are even books on Anti-Patterns describing how to detect/analyze what is not good in the code/script you’re inheriting/writing.
Learning design patterns can be fun, make you more productive, and a better PowerShell scripter. They are concepts that apply across languages and projects.
Note: Design Patterns was inspired by books written by Christopher Alexander on architecture and building
How Did We Get Here
Chris Hunt and I have collaborating for the past few years in different capacities. He reached out to me with ideas and ultimately a pull request on an open source project I put together PowerShellHumanizer. It didn’t stop there.
We interact regularly, sharing ideas, both good and bad (on both sides), giving and receiving feedback on approaches, code and more.
Recently we chatted about going from PowerShell scripter to PowerShell open source scripter and what that takes. More on that in later blog posts.
I brought up design patterns and started sharing scripts on implementing the in PowerShell using classes. This led to chats about testing, automated testing, testing in both PowerShell Classic and PowerShell Core. This opened conversations to using PowerShell classes (which have challenges), blending it with scripting and delivering solutions that were pure PowerShell and still had the intent of a Design Pattern.
As will anything, once you start trying these ideas out, it leads to many questions, answers and trade offs.
In addition, the best place to try these new practices out is on a set of PowerShell scripts you’ve implemented. You already have a solution to a problem, and you can redo it with the new ideas so you can weigh the benefits.
That is what Chris did. I helped by working up examples of Design Patterns in PowerShell, and the he an I would set time to discuss issues, blockers, successes, trade-offs and next steps.
We’ve only just got started, and we’ve learned a lot. There is more to come.
Getting Started
To kick off, we’ll look at two patterns, I’ll cover the Adapter Pattern
in this post and Chris re-worked a PowerShell project and implemented the original Gang of Four (GoF) Strategy Pattern.
Here’s Chris’ write up and implementation. Pay attention to the strategy pattern implementation, PowerShell classes and the fluent interface that Chris enabled.
Enjoy!
Adapter Pattern in PowerShell
Intent
The adapter pattern, also known as Wrapper, allows the interface of an existing item to be used as another.
PowerShell Example
Both of these PowerShell statements return 3. The underlying .NET Array has a Length
property and PowerShell employees the adapter pattern
(wrapper) so you can use Count
(1,2,3).Count
(1,2,3).Length
Here is a part of the $PSHOME\types.ps1xml
file that makes this happen. The Count
property uses the Length
property to print the results.
<Type>
<Name>System.Array</Name>
<Members>
<AliasProperty>
<Name>Count</Name>
<ReferencedMemberName>Length</ReferencedMemberName>
</AliasProperty>
</Members>
</Type>
This is a very useful technique. For example, when you have a set of objects that do similar things but you need to call a different property/method on each. Using the adapter pattern
, you can wrap the objects so they have a consitent way to call them. This super simplifies your code and you’ll see this in action at the end of this post.
Adapter Pattern In Action
Below we’ll use three PowerShell classes. A class declaration is like a blueprint that is used to create instances or objects at run time. More on PowerShell Classes
The first three classes are Computer
, Synthesizer
and Human
. The last class is Adapter
which has the code that’ll let us adapt
the others.
Each of the classes has a method that returns a string. Computer
has Execute
, Synthesizer
has Play
and Human
has Speak
. If we want to print these strings, we’d have to know which one we were working with and then call the correct name to get the result. It’d be much easier if we could call the same method on each. It would simplfy our code, wouldn’t need a switch or if statements, the script would be short and there would less of a chance of introducing errors.
class Computer {
[string] Execute() {
return "$this executes a program"
}
}
class Synthesizer {
[string] Play() {
return "$this is playing an electronic song"
}
}
class Human {
[string] Speak() {
return "$this says hello"
}
}
class Adapter {
[object] static Adapt($obj, $alias, $referenced) {
# doesn't work
# return $obj | Add-Member -PassThru -Force -MemberType ScriptMethod -Name $alias -Value {$this.$referenced}
# Use Invoke-Expression
return ("`$obj | Add-Member -PassThru -Force -MemberType ScriptMethod -Name $alias -Value {`$this.$referenced}") | Invoke-Expression
}
}
Let’s just pick Execute
as the name we want to call on each to get the string back. First we’ll create an array $targetObjects = @()
to hold Computer, Synthesizer and Human. That way we can leverage PowerShell’s capability to unroll the array and call a mehod on each like this $targetObjects.Execute()
.
$targetObjects = @()
$targetObjects+= [Computer]::new()
$o=[Synthesizer]::new()
$targetObjects+=[Adapter]::Adapt($o, "execute", "play()")
$o=[Human]::new()
$targetObjects+=[Adapter]::Adapt($o, "execute", "speak()")
$targetObjects.Execute()
With PowerShell classes, you need to create a “new” one by instantiating it. $o=[Computer]::new()
is one way to do it and $o=New-Object Human
is an alternate way. Here’s how we adapt or wrap an existing method to a new name. Or think of it like the way PowerShell makes an AliasProperty Count
for Length
$targetObjects+=[Adapter]::Adapt($o, "execute", "play()")
[Adapter]::Adapt
takes three parameters, an object $o
(Synthesizer) and a string for the alias "execute"
and a string for the referenced name "play()"
. Under the covers, the [Adapter]::Adapt
method then uses the PowerShell Add-Member
to make the “alias” point to the existing method.
You do this for each object and now $targetObjects.Execute()
works like a charm.
Summary
This is a powerful addition to your toolbox. As you progress to building toolkits and frameworks in PowerShell, knowing how to emply this technique will make you more productive. Plus, this is only one of the twenty three patterns in the original Design Patterns book, and because PowerShell is a dynamic language, there are other ways to leverage these approaches to make your scripts more maintainable and easier to read.
So, stay tuned and invest some time into learning how Design Patterns can make you a better thinker, communicator and implementor.