1

I'm trying to implement the Builder design pattern in a Jenkins pipeline shared library. Say I have the following file structure and pseudo-code (to only illustrate the concept I'm after)

src/com/company/builders/
                        /Builder.groovy
                        /WidgetXBuilder.groovy
                        /WidgetYBuilder.groovy

Builder.groovy has

package com.company.builders

abstract class AbstractBuilder {
  protected String part0 = null
  protected String part1 = null
  protected String widget = null
}

// Implement in Widget builder classes
def buildWidget(final String p0, final String p1) {
}

// Used by any Widget concrete class
def getWidget() {
  return widget
}

WidgetXBuilder.groovy has

package com.company.builders

class WidgetXBuilder extends AbstractBuilder {
}

// Overload buildWidget()
def buildWidget(final String p0, final String p1) {
  widget = p0 + p1
}

Similarly, WidgetYBuilder.groovy has

package com.company.builders

class WidgetYBuilder extends AbstractBuilder {
}

// Overload buildWidget()
def buildWidget(final String p0, final String p1) {
  widget = p0 - p1
}

Now I simply want a client to do this. Note my-shared-library is a shared library that I set up in my Jenkins system configuration that has the Git repo of above file structure.

@Library('my-shared-library') _
import com.company.builders.Builder
import com.company.builders.WidgetXBuilder
import com.company.builders.WidgetYBuilder

// Build WidgetX
Builder widgetx = new WidgetXBuilder()
widgetx.buildWidget("part0", "part1") // Build a WidgetX widget
widgetx.getWidget() // get the WidgetX widget

// Build WidgetY
Builder widgety = new WidgetYBuilder()
widgety.buildWidget("part0", "part1") // Build a WidgetY widget
widgety.getWidget() // get the WidgetY widget

Can I achieve this with shared libraries? That is, implement the concept of encapsulation, inheritance, and polymorphism using a shared library? How? TIA

1

3 Answers 3

1

Yes, The structure provided in the question is correct.

With the setup provided above, it will achieve encapsulation through the use of an abstract base class (AbstractBuilder), inheritance by extending the base class in the widget builder classes (WidgetXBuilder and WidgetYBuilder), and polymorphism by overriding the buildWidget method in each builder class.

The only change would be to use using def widgetx = new WidgetXBuilder() instead of Builder widgetx = new WidgetXBuilder(), it allows for flexibility and dynamic typing in Groovy.

This means that the type of the variable widgetx is determined by the type of the object created on the right side of the assignment (new WidgetXBuilder() in the code provided in your question).

Here is an example pipeline (do not need to import com.company.builders.WidgetXBuilder etc. explicitly in your pipeline script if you are using the shared library):

@Library('my-shared-library') _

def buildWidget(AbstractBuilder builder, String part0, String part1) {
  builder.buildWidget(part0, part1)
  return builder.getWidget()
}

pipeline {
  agent any
  
  stages {
    stage('Build Widgets') {
      steps {
        script {
          def widgetX = buildWidget(new com.company.builders.WidgetXBuilder(), "part0", "part1")
          echo "WidgetX: ${widgetX}"
          
          def widgetY = buildWidget(new com.company.builders.WidgetYBuilder(), "part0", "part1")
          echo "WidgetY: ${widgetY}"
        }
      }
    }
  }
}

0

Here's a very simple on how I implemented it. It works flawlessly for my needs. The lesson I learned is that the class name must match the file name, and as a corollary, there can only be one class definition per file.

File: Builder.groovy

package com.company.builders
abstract class Builder {
  def walk() {
    throw new Exception("Needs to be implemented in concrete classes")
  }

  protected Builder(final String color, final Integer numLegs) {
    this.color = color
    this.numLegs = numLegs
  }

  // Common to all widgets
  protected def changeColor() {
    // do something
  }

  // Attributes
  protected String color = null
  protected Integer numLegs = -1
  protected String shape = null
}

File: WidgetXBuilder.groovy

package com.company.builders
import com.company.builders.Builder
class WidgetXBuilder extends Builder {
  WidgetXBuilder(final String color, final Integer numLegs) {
    super(color, numLegs)
  }

  def walk() {
    changeColor()
    // Implement Widget X walk function
  }

  protected String shape = "round"
}


File: WidgetYBuilder.groovy

package com.company.builders
import com.company.builders.Builder
class WidgetYBuilder extends Builder {
  WidgetYBuilder(final String color, final Integer numLegs) {
    super(color, numLegs)
  }

  def walk() {
    changeColor()
    // Implement Widget Y walk function
  }

  protected String shape = "square"
}


Jenkinsfile

@Library('shared-library') _
import com.company.builders.WidgetXBuilder
import com.company.builders.WidgetYBuilder

node("linux") {
  def obj = new WidgetXBuilder("blue", 2)
  obj.walk() // walks like a blue widget X with 2 legs
  obj = new WidgetYBuilder("green", 4)
  obj.walk() // walks like a green widget Y with 4 legs
}
-1

Inheritance is neither well-supported nor recommended for Jenkins pipeline. It may lead to runtime issues. Please refer these articles:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.