技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Circuit Playground Express の各種機能を試す

環境:

  • Circuit Playground Express (Bootloader3.10.0)
  • Circuit Python v5

ボタン AB 認識

import board
import time
from digitalio import DigitalInOut, Direction, Pull

# LED definition
led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT

# A button definition
ButtonA = DigitalInOut(board.BUTTON_A)
ButtonA.direction = Direction.INPUT
ButtonA.pull = Pull.DOWN # Dont push value define.

# B BUTTON DEFINITION
ButtonB = DigitalInOut(board.BUTTON_B)
ButtonB.direction = Direction.INPUT
ButtonB.pull = Pull.DOWN

# Button change delay
delay = 0.1

# Execution loops.
while True:
    delay = 0.05 if ButtonA.value else 0.1
    delay = delay * 10 if ButtonB.value else delay
    led.value = True
    time.sleep(delay)
    led.value = False
    time.sleep(delay)

やってみると分かるけど、ボタンを押している間だけ LED の点滅速度が変化するやつ。
CircuitPython で利用できる基本 APIこのへん に落ちてた。
うんドキュメント、ないね☆

カラー LED を光らせる(簡易イルミネーション)

import time
import board
import neopixel

# neopixcel って LED の名前らしい。てか Pixcel というだけあって、RGB カラーで光らせられる
npx = neopixel.NeoPixel(board.NEOPIXEL, 10, auto_write=False)
loop = 0

# カラーテーブルを定義して
colors = [
    (128, 0, 128),
    (255, 0, 0),
    (128, 128, 0),
    (0, 255, 0),
    (0, 128, 128),
    (0, 0, 255)
]

while True:
    # カラーテーブルの順序に従って色を変える
    # 光らせる順序は反時計
    loop += 1
    loop = 0 if loop >= len(colors) else loop
    col = colors[loop]

    for pp in range(10):
        npx[pp] = col # RGB で書き込む
        npx.show()
        time.sleep(0.2)

f:id:white-azalea:20200630190229j:plain

Circuit Playground Express をいじったログ

一通り機能を触ったので、紹介記事と、初期セットアップだけ。

経緯

買って試したのはコレ

Circuit Playground Express

Circuit Playground Express

  • メディア: おもちゃ&ホビー

当初、IoT として一度ブームになったときは、RasberryPi がやたらと人気で、記事は実質それ一色だったのを覚えてる。
ただ、買って弄ってわかったことは、あれは規模の小さい PC であったという事実だ。
それで何が言いたいかって言うと

ハード設計できない人はお呼びでない ということだった。
もともと Web 屋さんで、ハードなんて大学でマイコンアセンブラ書き込んでLED と高額センサーで信号通信してみた位しかやったことがない。
まして就職して 当時 8 年目位で、今更回路設計しろって言われても無茶振りに近かった…要するにそんな余裕も金もなくて挫折した(汗
(Web屋さん業界じゃ HTML5ecmascript や各種フレームワーク打なんだで群雄割拠ヒャッハーしてたり AI たのすぃーとかいってフラフラしてた)

で、しばらく遠のいてたところを、知人にこんな本紹介されて

booth.pm

え?マジで?と調べて驚いた。

Circuit Playground Express とは

平たくいえば、ネットワーク危機こそないけど、ジャイロ、温度、音声、スピーカー、LED、タッチセンサー、赤外線と、コミコミ全盛り回路セットである。
要するに、多くのソフトやさんなプログラマが挫折するだろう箇所を全盛りで用意してくれてるという一品。

コレは軽くチートじゃないんですかね…(汗

開発準備

Bootloader

USB で繋いで、中央のスイッチを押すと USB 認識するので、ブートローダのアップデートを行う。

Mac でやってたので https://learn.adafruit.com/adafruit-circuit-playground-express/updating-the-bootloader こっからダウンロードして更新した。
update-bootloader-circuitplay_m0-vX.XX.X.uf2 を finder にドロップするだけで、中にある INFO_UF2.TXT の中に記述されるバージョンなどが上がった。
バイス的にもこの辺を事前サポートしてるのか…ううむ…。

この記事時点では v3.10.0 が最新だった。

CircuitPython

circuitpython.org

ここから CircuitPython をダウンロード & インストール。
ここでは 5.3.0 の英語版を選択。
ライブラリも Libraries から入手した。

日本語なんてドマイナー言語対応してないのはある意味仕方ないよな(汗

で、ここで気をつけるべきは、全部のライブラリは容量的にはいらないということ…
なので、 Python コード上使用しているものだけ選択して入れる必要があるという点。

ここで全パターンとライブラリ紹介始めると、時間がいくらあっても足らないので省略。

最も単純な LED 点灯

といっても写経だったりするのだが

f:id:white-azalea:20200628203254p:plain

ここの code.py がエントリポイントになってるようなので、コレを弄る。

import board
import time
from digitalio import DigitalInOut, Direction

led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT

while True:
    led.value = True
    time.sleep(0.1)
    led.value = False
    time.sleep(0.1)

色々疑問なので、import してるやつを追いかける。
こういう調査がないと応用効かない…

まず、circuit python の公式リポジトリ

GitHub - adafruit/circuitpython: CircuitPython - a Python implementation for teaching coding with microcontrollers

で、公式ドキュメント漁ると発見 https://learn.adafruit.com/welcome-to-circuitpython/creating-and-editing-code コレか。

で、やっぱそうなのかという話で、 board, time, digitalio組み込みライブラリ であるそうな。

  • board gives you access to the hardware on your board (ボードはボードに存在するハードウェアへのアクセスを提供します) *digitalio lets you access that hardware as inputs/outputs (digitalio はハードウェアへ Input/Output を提供します)

ナルホド、で、なんで D13 なん?って思ったらあっさりわかった。

f:id:white-azalea:20200628205651p:plain

あーうんなるほど…既にそこそこ抽象化してるのね(汗
じゃー何がビルトインされてんのさ?って思ったら

https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-built-ins https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-digital-in-out

ふむふむ。

で、 Circuit Python のライブラリには何あんのさ?って思って調べたら

circuitpython.readthedocs.io

しかもハードごとに違うってんだからコレは一瞬「うぉっ」となるよね(汗

デバッグ環境を整える

で、これなにかバグった時にデバッグ出力得られないと泣けるので

https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console#using-something-else-2978927-11

この辺を利用する。

何が泣けるって、ここで推奨してる Mu Editor が Mac 10.15.5 で起動しないもの…セキュリティは分かるがちっとヒステリックになってきてないかと思わなくない。
時期バージョンで Arm 移行もあるし、結局「開発者<消費者」ルートで開発者を置いてけぼりにするんかね… Windows もアレだし、Ubuntu に戻ろうかな…。

現在のシリアルコネクションを確認

f:id:white-azalea:20200628210739p:plain

USB 接続なのは確実なので、/dev/tty.usbmodem144101 が結局デバイスだろうね。
てことで screen /dev/tty.usbmodem144101 115200 と叩く

f:id:white-azalea:20200628211448p:plain

これで画面が真っ黒になるので、コード中にデバッグ文字列を挿入。

f:id:white-azalea:20200628211804p:plain

やってみれば分かるが、デバイスに保存した次の瞬間には適用されてる。
CPU は 48MHz なので、Windows95 黎明期?逆に言えばそれくらいのスペックがこんなボードに乗るようになったんだなーと実感(汗

メモリが 2MB しかないというのも割とネックではあるので、ラズパイと組み合わせてやるとネットワーク連携もできて幸せになれそうな気がするよ。

機械学習で使う数学系関数…つーか数式

相変わらず Puthon 3.7.x (anaconda) で検証。

シグモイド関数

基本形はこんな数式らしい。

 f _ \theta(x) = \frac{1}{1 + e ^ {-\theta ^ Tx}}

グラフにしてみるかね

import numpy as np
import matplotlib.pyplot as plt
import math
%matplotlib inline

def sigmoid(x):
    """シグモイド関数"""
    e = math.e
    return 1/(1 + e**-x)

x = np.arange(-20, 20, 0.5)
y = sigmoid(x)

fig = plt.figure(figsize=(10.0, 5.0))

plt.plot(x, y, color='blue')

plt.grid()
plt.show()

f:id:white-azalea:20200618235120p:plain

よくニューラルネットワークニューロンにされるやつ。

まぁ要するに 0 - 1 の間、0 でちょうど 0.5 となってるので、パーセンテージを扱う処理に向いてるって話らしい。
これで単純な二値判定するなら、0.5 が当然のようにボーダーになるわけで…

0 がちょうど 50% なので、それがプラスかマイナスかで 1/ 0 と判定すればいいので


  y =
  \begin{cases}
     1 \quad (\theta ^ T \geq 0) \\
     0 \quad (\theta ^ T \leq 0) \\
  \end{cases}

という形になる。

これで長方形判定をするものと考えると。
横長である確率を求めると考えて、横軸を x _ 1 縦軸を x _ 2 として適当なパラメータを与えてみる(あくまでも適当)


 \theta =\left[
    \begin{array}{rrr}
        \theta_0 \\
        \theta_1 \\
        \theta_2
    \end{array}
  \right] = \left[
    \begin{array}{rrr}
        -50 \\
        4 \\
        2
    \end{array}
  \right], \quad x = \left[
    \begin{array}{rrr}
        1 \\
        x_1 \\
        x_2
    \end{array}
  \right]

式に直すと


    \theta ^ Tx = -50 \cdot 1 + 4x _ 1 + 2x _ 2  \geq 0 \\
    2x _ 2 \geq 50 - 4x _ 1 \\
    x _ 2 \geq 25 - 2x _ 1

図にすると

続きを読む

行列使った機械学習で、縦長/横長長方形を判定する

問題

四角が存在して、縦長か横長かを判定する。
正直それだけなら座標見れば一発だが、敢えてベクトル的に考えてみる。

横幅 縦幅
80 150 縦長
60 110 縦長
35 130 縦長
160 50 横長
160 20 横長
125 30 横長

これをプロットすると

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

x = np.array([80, 60, 35, 160, 160, 125])
y = np.array([150, 110, 130, 50, 20, 30])

fig = plt.figure(figsize=(5.0, 5.0)) # グラフサイズ

plt.scatter(x, y, marker = 'o')

plt.grid()
plt.show()

f:id:white-azalea:20200616231234p:plain

これを線引きする直線を求めたい。
これを行列的に求めるなら、重みベクトルを法線ベクトルとした直線 という言葉になる…らしい。

うーん難しい (;^ω^)

重みベクトルを $w$ とすると直線の方程式は

w \cdot x=0 もっというと  w_1 \cdot x _ 1 + w _ 2 \cdot x _ 2 = 0
ってなるので、 w = (1, 1) と仮定すると x _ 1+ x _ 2 = 0 ということは


x _ 1 = -x _ 2

これを図にすると

fx = lambda x : -x
x = np.arange(-10, 10, 0.5)
y = fx(x)

fig = plt.figure(figsize=(5.0, 5.0))

plt.plot(x, y)

# w = (1, 1) も作画
plt.quiver(0, 0, 1, 1, angles='xy', scale_units='xy', scale=1)

plt.grid()
plt.show()

f:id:white-azalea:20200616231306p:plain

この論理で、判定できる関数を学習で習得させる。
目的の重みベクトルを w として、学習式を以下にしてみる。


w :=
  \begin{cases}
     w+y ^ {(i)}x ^ {(i)} \quad f_w(x ^ {(i)}) \neq y ^ {(i)} \\
     w \quad\quad\quad\quad f_w(x ^ {(i)}) = y ^ {(i)} \\
  \end{cases}

縦長をこのベクトルと内積とったときには正、横長をこのベクトルと内積とったときに負と判定するとして、成功するなら何もしない。
判定に失敗したらベクトルの足し算を行うと…

from copy import copy

# 学習データ
values = [
    np.array([80, 150]),
    np.array([60, 110]),
    np.array([35, 130]),
    np.array([160, 50]),
    np.array([160, 20]),
    np.array([125, 30])
]
actuals = [
    True, True, True, False, False, False
]

# 正規化
def normalize(v):
#     c = np.linalg.norm(v)  # ベクトルの長さを求めて
    return v #(v / c)

# 学習データ
w = np.array([-1, -1])

# 学習の変遷
log = [ w ]

# 学習関数
for n in range(0, 6):
    v = normalize(values[n])
    actual = actuals[n]
    res = np.dot(w, v)
    res = float(res)
    
    print('--------')
    print('current:' + str(w))
    print('test     :' + str(v))
    print('res        :' + str(res))

    # 正常判定したら何もしない
    if (res > 0) and actual:
        continue
    elif (res < 0) and (not actual):
        continue
    
    # 判定に失敗してたら更新
    if actual:
        w = w + v
    else:
        w = w - v
    log.append(copy(w))
    print('update to:' + str(w))
    
    print(log)

出力ログこんな感じ。

    --------
    current:[-1 -1]
    test     :[ 80 150]
    res        :-230.0
    update to:[ 79 149]
    [array([-1, -1]), array([ 79, 149])]
    --------
    current:[ 79 149]
    test     :[ 60 110]
    res        :21130.0
    --------
    current:[ 79 149]
    test     :[ 35 130]
    res        :22135.0
    --------
    current:[ 79 149]
    test     :[160  50]
    res        :20090.0
    update to:[-81  99]
    [array([-1, -1]), array([ 79, 149]), array([-81,  99])]
    --------
    current:[-81  99]
    test     :[160  20]
    res        :-10980.0
    --------
    current:[-81  99]
    test     :[125  30]
    res        :-7155.0

プロットしてみるとこうなる。
ちなみに最終的な法線も入れて作画。

x = np.array([80, 60, 35, 160, 160, 125])
y = np.array([150, 110, 130, 50, 20, 30])

def nvec(v):
    # 法線ベクトル
    # 99 / -81 = -1.2222222222222223
    # 81 / 99
    return v * 0.8181818181818182

nx = np.arange(-180, 180, 1)
ny = nvec(nx)

# 正規化関数
def normalize(v):
    c = np.linalg.norm(v)
    return (v / c) * 80.0

# 計算の過程で出た重みベクトルのリスト
plotData = []
for v in log:
    plotData.append(v)

fig = plt.figure(figsize=(8.0, 8.0)) # グラフサイズ
plt.xlim(-180, 180)
plt.ylim(-180, 180)

# 学習データ
plt.scatter(x, y, marker = 'o')
# 縦長か横長かを判定する法線ベクトル
plt.plot(nx, ny, color='blue')

# 学習中に出たベクトルの変遷
# 赤を基調に、だんだん青が交じる
color = [1.0, 0.0, 0.0]
bias = 0.2
for v in plotData:
    plt.quiver(0, 0, v[0], v[1], angles='xy', scale_units='xy', scale=1, color=color)
    color[0] = color[0] - bias
    color[2] = color[2] + bias


plt.grid()
plt.show()

f:id:white-azalea:20200616231523p:plain

これで無事青の法線が引けた。
当たり前だけど、学習サンプルが完全ではないので、この法線ベクトルでは縦長か横長かを完全に判定することはできない…が、それが縦長か横長かを学習する学習機はこんな感じでやれる。

多分アヤメ判定とか、ベクトルでの二値判定で使えるはず。

ベクトルは全部グラフにしてみないとわかりにくくてたまらない(汗

テスト駆動開発

書籍

テスト駆動開発

テスト駆動開発

  • 作者:Kent Beck
  • 発売日: 2017/10/14
  • メディア: 単行本(ソフトカバー)

来たよケントベック先生(Smalltalker で XP 開発の始祖様)!

読もうと思ったきっかけ

テスト駆動開発…って聞いた時の自分の知ってる知識は

  1. ユニットテストを最初に書いて、以下を定義する
  2. どう動くべきか
  3. どう書きたいのか
  4. つまりコードが仕様書なんだ
  5. テストを通る様に実装する

でしかなかった。
要するに 我流な TDD のイメージ を持ってたわけだ。

なので、改めて勉強してみるのもいいかなと

意識 Before/After

Before

我流TDD 的に考えていれば、テストを書く前の段階からこうしておく必要があると思い込んでた

  • どういった機能が必要なのかを事前に設計しなければならない
  • インターフェースの定義や、設計の有るべき論を定義してかかるべきである

だから テスト駆動は難しい ので スキルのあるチームでないと出来ない事だ とそう 勝手に思い込んでいた

After

ああ、TDD とは 設計まで対象にリファクタを行う 開発様式だったのかと初めて知った。

基本の1サイクルは以下の通り。

  1. テストを書く
  2. コンパイルを通す(空実装)
  3. テストを走らせて失敗を起こす
  4. テストを通す
  5. 重複の排除

ただしこれを、1モジュールあたり以下のフェーズで行う。

  1. 最低限必要とすべき機能を定義する(実装は適当でいい)
  2. リファクタ(テスト上、内部変数の変化をチェックする様な使い方は微妙とか、インターフェースの見直し)
  3. オブジェクトとして見た時に、他に必要な物がないかの観点でインターフェース定義
  4. テストコードのリファクタ
  5. テストのバリエーション(こんな時はどうあるべきよ?)の作成
  6. テストから見た同一概念操作の抽出
  7. 派生型/類似型などの隠蔽化(クラスの同一化か派生クラスの削除など…)
  8. 不要となったテストの除去

何てこった、 実装だけじゃない、 インターフェース…使い方までサイクルに入ってるやんけ

ざっくり内容と見所

  1. 第一部: 金額の取り扱いクラスについてTDDの実装を行ってみる(ハンズオン)
  2. 第二部: xUnit (ユニットテストのほぼデファクトスタンダード)の構成と、考え方(ハンズオン)
  3. 第三部: テスト駆動パターン(読み物)
  4. テスト駆動で記述するテストの種類
  5. テスト手法のパターン
  6. 仮実装から本実装へのリファクタを行うテスト修正パターン
  7. xUnit の技法
  8. デザインパターン(!?まさかの設計パターン)
  9. リファクタリング手法
  10. TDD の身につけ方

という構成。
手を動かさないと退屈という自分にはすごく有り難い構成でした。

ってーか

第三部盛り過ぎでしょ(汗
消化不良ならもう何でも関連資料漁る位は当然しますけどさ…(汗

本気で第三部だけで1冊出ても良いと思いました。第三部だけでも技法技術大好きっ子には (^q^) ですよマジで…

プログラマに子供がいるなら一緒に読みたい本

今回は紹介エントリです。

先月位から Kindle unlimited を契約してまして、ちまちま本を読むのですが、お堅い本読んでるとたまに息抜き本読んでみようかなとか思うわけです。
で、親が教員であることもあり、どうかなーと思って拾って読んでみたら意外と良書。
何か広めたいなーと思ったので書いてます。

内容

  • 内容: 画面表示を最優先に、CUI-GUI の出し方をまず学び、1マップRPG/簡易 ADV (紙芝居)ゲームまで説明してる。
  • 深さ: コントローラを使うといった事はしていないが、画面上にボタンや選択肢を置いてポチポチゲーを作るまでは説明。
    簡単なスクリプトまで用意していて、説明もしっかりしてるので、独自命令何かも説明範囲の知識でできない事はない。
  • 作成内容のクオリティ: かろうじて静止画を読み込んで配置する位なので、3D云々を期待する昨今の子供が期待するレベルかというと微妙。
    (というかそういう子供の期待するレベルを満たせるレベルで作る技量があれば、ゲーム業界でガリガリ作れやって話だが…)
  • 文量: Python 触ったことある開発者なら、読むだけなら 40 分で読み切れるレベル。実装しても1日かからないと思われる文量。

ただ、ツクールに頼らず簡単なツクールゲーを実装する位にはなれるかも?

どの辺が良いと思った

自分が見てていいなーと思ったのが以下

  • 環境のセットアップが最短単純
    初心者が真っ先に躓くポイント。多分一番躓きの少ないシンプル+スタンダードを貫いてる。
  • 子供の一番やりたいこと(ゲームらしい物を早く作ってやりたい)を優先して、細かい概念や言語機能は極力触れない。
    プログラマなら欠点にも見えるかも知れないが、「書いて直ぐ結果が出る」は初学者の導入には必須。
    (なので言語としてのPHPは大嫌いだが、初心者導入用言語としてはいい言語だと思ってる)
  • 独自の「こんなこともあろうかとライブラリ用意しておきましたー」が無い
    ゲーム開発本あるあるで、独自のライブラリを組み込んでゲームの必要概念を説明する本は昔からいくらでもある。
    が、それを行ってしまうとプログラミングの入門者が「何それ?」ってなる上に、ライブラリが用意されなければ何もできないピクミンと化す。
    しかしこの本、ド標準ライブラリしか使ってない。その姿勢はとてもよい。
  • 使ってる言葉は 12 歳には少ししんどい → 逆を言えば親子で読むのに向いてる
    ただしコレを子供に一人で読ませるのか?には少し疑問符。
  • 写経だけじゃなくて、エラーが出た時、エラーの理由に関してもよくあるパターンの説明。
    多くの本はこういう所説明してない。

気を付けるポイント

「拡張子」とか「フォルダ」とか、パソコンの一般用語ではあるが、それらが何の説明もなく出てくる。
そのため、親がパソコンを仕事でしか触らない程度の一般職だと、説明しきれない。

ここまでいうと「情弱じゃねーか」ってツッコミが来そうだが、日本一般人の情弱を甘く見てはいけない…
買ったPCに MicrosoftOffice が入ってないとクレーム入れる人間が多い位情弱国家なのだ…

なので、親子で読もうと思ったら、少なくとも パソコン検定4-5級程度の知識はあったほうがいい。 (PCがざっくりどんなパーツがあって、どんなデバイスがあって…とか、OS/アプリ、フォルダやデスクトップの説明、Windows の基本操作程度)

Salesforce で DI できないか考えてみた

DI って?

Dependency Injection 外部依存注入の略。
DI の説明が要旨でもないので、下記 URL 参照。

qiita.com

Salesforce で DI ?

Salesforce のアプリケーションをパッケージで公開するとき、やってみるとわかるのですが、「global」修飾したオブジェクトしか利用者はカスタムできません。
そこは別に良いのですが、「色々カスタマイズできるようにしたい」とあれこれ global 指定してしまうと、以下のジレンマに悩まされます。

global インターフェースは絶対に変更できないので、公開すればしただけ変更ができない

ということになります。
特に、継承を許してしまうと致命的ですね。
内部挙動に依存したコードを書かれて、何か変更しようものなら「バグだゴルァ!」とクレームが飛ぶこと請け合いです。

そこで、機能を分離して、設定で上書きしたり継承したりできるようにできないか?
といったときに

インターフェースだけ公開して好きに実装しろ

と、内部挙動を勝手に実装しろ、ただしインターフェースの仕様は守れよと。
そんな感じでカスタムできる様にする手段として DI を考察しました。

基本原理

クラス名さえわかれば、引数なしコンストラクタ経由でインスタンスを生成できることがわかっているので、

  • カスタム設定に「インターフェース名=実装クラス名」を実装させる
  • コンテナがこれをみてインスタンスを生成する(未設定ならデフォルトの設定クラスをインスタンス化する)
  • インターフェースに依存する形でアプリケーションを作成する

としておけば、顧客はカスタム設定を変更するだけで、実際の挙動を変更したりカスタムしたりできるようになるはずだ。

ということで検証した。

いざ実装

まずは検証対象クラスを作成

実際にはルートクラスと、インターフェースは global にでもするのだろうがここでは割愛。

public with sharing class SimpleExample {
    public interface ExampleInterface {
        String getMessage();
    }

    public class DefaultImpl implements ExampleInterface {
        public String getMessage() {
            return 'Default implements';
        }
    }
}

DI 置き換え対象のクラスも作成

public with sharing class CustomImpl implements SimpleExample.ExampleInterface {
    public String getMessage() {
        return 'Custom implements';
    }
}

ここまでは大した難しい話ではないかも?

カスタム設定を追加する

f:id:white-azalea:20200412170152p:plain
という事で Salesforce 上に設定した図

設定の内容はあくまで検証用サンプルなので

  • API 参照名
    DISample__c
  • 項目
    ExampleInterface__c : String(32)

DI コンテナマネージャの作成

DI を実際に行っていくクラスを作成する。
ここも実運用だと global だったり、ネームスペース指定とかカスタム設定を受け取るとか色々ありそうだが、今回はあくまで検証なので割愛して実装した。

public with sharing class DIManager {

    public class DISetting {
        private String interfaceName { get; private set; }
        private String settingName { get; private set; }
        private Type defaultObjectType { get; private set; }

        public DISetting(String interfaceName, String settingName, Type defaultObjectType) {
            this.interfaceName = interfaceName;
            this.settingName = settingName;
            this.defaultObjectType = defaultObjectType;
        }
    }

    private Map<String, DISetting> diSettings;

    public DIManager(List<DISetting> settings) {
        this.diSettings = new Map<String, DISetting>();
        for (DISetting st : settings) {
            this.diSettings.put(st.interfaceName, st);
        }
    }

    public Object getObject(String interfaceName) {
        DISetting setting = this.diSettings.get(interfaceName);
        if (setting == null) { return null; }  // not found

        DISample__c diSetting = DISample__c.getOrgDefaults();
        String targetObjectName = diSetting != null ? (String) diSetting.get(setting.settingName) : null;

        if (String.isEmpty(targetObjectName)) {
            return this.getDefaultObject(interfaceName);
        } else {
            Type t = Type.forName(targetObjectName);
            return t.newInstance();
        }
    }

    private Object getDefaultObject(String interfaceName) {
        DISetting setting = this.diSettings.get(interfaceName);
        if (setting == null) { return null; }  // not found
        return setting.defaultObjectType.newInstance();
    }
}

検証

色々面倒なので匿名 Apex 実行をしてみる。
ソースは下記

DIManager.DISetting setting = new DIManager.DISetting('ExampleInterface', 'ExampleInterface__c', SimpleExample.DefaultImpl.class);
DIManager manager = new DIManager(new List<DIManager.DISetting> { setting });

SimpleExample.ExampleInterface example = (SimpleExample.ExampleInterface) manager.getObject('ExampleInterface');

System.debug('Message : ' + example.getMessage());

まずは、カスタム設定を利用せずに実行する。

(前略)
Execute Anonymous: System.debug('Message : ' + example.getMessage());
17:05:25.25 (25031288)|USER_INFO|[EXTERNAL]|0052w000003PbHm|arstromeria@white-azalea.net|(GMT+09:00) 日本標準時 (Asia/Tokyo)|GMT+09:00
17:05:25.25 (25094245)|EXECUTION_STARTED
17:05:25.25 (25103087)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
17:05:25.25 (30135496)|USER_DEBUG|[6]|DEBUG|Message : Default implements  ← 特に設定が存在しないのでデフォルト実装が動作してる
17:05:25.30 (30230158)|CUMULATIVE_LIMIT_USAGE
17:05:25.30 (30230158)|LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 0 out of 100
  Number of query rows: 0 out of 50000
(以下略)

ではここで、カスタム設定を行ってみる。

f:id:white-azalea:20200412170848p:plain

この状態で実行すると

17:09:13.33 (33341882)|EXECUTION_STARTED
17:09:13.33 (33360008)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
17:09:13.33 (42787189)|USER_DEBUG|[6]|DEBUG|Message : Custom implements  ← 設定によって利用されるインスタンスが書き換わった!
17:09:13.42 (42932602)|CUMULATIVE_LIMIT_USAGE
17:09:13.42 (42932602)|LIMIT_USAGE_FOR_NS|(default)|

ということができた。