見栄えがよくない場合以外に、Javaでスイッチケースを実装する別の方法はありますか。対応するメソッドを実行する必要がある選択に応じて、一連の値が組み合わされて存在します。
14 に答える
コードの周りにたくさんの switch/case ステートメントがあり、それらがあなたを夢中にさせている場合。
Refactoring: Replace conditional with polymorphism を選択できます。
情報をさまざまなデバイスに保存するために使用されるソフトウェアがあるとします。4 つの永続化操作が定義されています: fetch、save、delete、updateであり、N 個の永続化メカニズム (フラット ファイル、ネットワーク、RDBMS、 XMLなど)。
コードはそれらすべてをサポートする必要があるため、4 つの異なる場所にこれがあります。
前
class YourProblematicClass {
....
public void fetchData( Object criteria ) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file
// read it
// find the criteria
// build the data
// close it.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// retrieve the data
// build the data
// close connection
break;
case DataBasePersistace:
// Get a jdbc connection
// create the query
// execute the query
// fetch and build data
// close connection
break;
}
return data;
}
保存・削除・更新も同様
public void saveData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file, go to EOF, write etc.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// etc
break;
case DataBasePersistace:
// Get a jdbc connection, query, execute...
break;
}
}
等々....
public void deleteData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break;
case DataBasePersistace:
break;
}
}
public void updateData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break;
case DataBasePersistace:
break;
}
}
switch/case ステートメントの使用が問題になります。
新しいタイプを追加するたびに、各セクションに新しいスイッチ/ケースを挿入する必要があります。
多くの場合、一部のタイプは類似しており、別のスイッチ/ケースは必要ありません (カスケードできます)。
他のものもあれば、わずかに異なる場合もあります
実行時に異なるタイプをロードする必要がある場合もあります (プラグインなど)。
したがって、ここでのリファクタリングは、インターフェイスまたは抽象型を追加し、さまざまな型にそのインターフェイスを実装させ、責任をそのオブジェクトに委任することです。
したがって、次のようなものになります。
後
public interface PersistenceManager {
public void fetchData( Object criteria );
public void saveData( Object toSave );
public void deleteData( Object toDelete );
public void updateData( Object toUpdate );
}
そして、さまざまな実装
public class FilePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// open file
// read it
// find the criteria
// build the data
// close it.
}
public void saveData( Object toSave ) {
// open file, go to EOF etc.
}
public void deleteData( Object toDelete ){
....
}
public void updateData( Object toUpdate ){
....
}
}
そして、他のタイプはそのロジックに従って実装されます。ネットワークはソケットとストリームを処理し、DB は JDBC、ResultSet などを処理します。XML はノードなどを処理します。
public class NetworkPersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// Socket stuff
}
public void saveData( Object toSave ) {
// Socket stuff
}
public void deleteData( Object toDelete ){
// Socket stuff
}
public void updateData( Object toUpdate ){
// Socket stuff
}
}
public class DataBasePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// JDBC stuff
}
public void saveData( Object toSave ) {
// JDBC stuff
}
public void deleteData( Object toDelete ){
// JDBC stuff
}
public void updateData( Object toUpdate ){
// JDBC stuff
}
}
最後に、呼び出しを委任するだけです。
後で:
public YouProblematicClass { // not longer that problematic
PersistamceManager persistance = // initialize with the right one.
public void fetchData( Object criteria ) {
// remove the switch and replace it with:
this.persistance.fetchData( criteria );
}
public void saveData( Object toSave ) {
// switch removed
this.persistance.saveData( toSave );
}
public void deleteData( Object toDelete ){
this.persistance.deleteData( toDelete );
}
public void updateData( Object toUpdate ){
this.persistance.updateData( toUpdate );
}
}
そのため、タイプに応じて永続化マネージャの正しいインスタンスを 1 回だけ作成するだけで済みます。次に、すべての呼び出しがポリモーフィズムによって解決されます。これは、オブジェクト指向テクノロジの重要な機能の 1 つです。
別の持続性マネージャーが必要であると判断した場合は、新しい実装を作成してクラスに割り当てるだけです。
public WavePersistance implements PersistanceManager {
public void fetchData( Object criteria ) {
// ....
}
public void saveData( Object toSave ) {
// ....
}
public void deleteData( Object toDelete ){
// ....
}
public void updateData( Object toUpdate ){
// ....
}
}
おそらく、ケースが一定であるという要件に苦労しているでしょう。通常、これはコードの匂いですが、できることがあります。切り替えようとしている理由を詳述する別の質問を提起してリンクすることをお勧めします。
Map<String,Object> map = new HasMap<String,Object>();
// ... insert stuff into map
// eg: map.add("something", new MyObject());
String key = "something";
if (map.contains(key)) {
Object o = map.get(key);
}
上記の例では、次のような「ハンドラ」にマップすることができます。
interface Handler {
public void doSomething();
}
これにより、これがすべてルックアップになります。
if (map.contains(key)) { map.get(key).doSomething(); }
繰り返しますが、それは少しにおいがするので、理由を説明する質問を投稿してください.
ポリモーフィズムを使用するようにコードをリファクタリングすると、switch ステートメントが不要になる場合があります。ただし、スイッチには正当な用途がいくつかあるため、状況によって異なります。
の醜いシリーズif,else if,else
?
または、一種の動的な切り替えのケースを想像することができます:
public interface Task<T>
{
public void doSomething(T context);
}
public Class SwitchCase<T>
{
Map<Integer,Task<T>> tasks;
Task<T> defaultTask;
public void choose(int choice, T context)
{
Task<T> t= this.tasks.get(choice);
if(t!=null) { t.doSomething(context); return;}
if(defaultTask!=null) { defaultTask.doSomething(context);}
}
}
「Clean Code」には、switch/case と if/else に応じた素敵な章があると思います。
さらに、switch case、ポリモーフィズム、または古き良き if/else を使用して、「ノイズ」を削減し、コードをよりクリーンにすることができるかどうかを判断することは理にかなっていると思います。ここでは、ケースの数が重要な役割を果たしていると思います。
スイッチケースを列挙型に置き換えた典型的なケースを投稿します。
リファクタリングする前に私は列挙型を持っていますPatternTypes
:
public enum PatternTypes {
ALPHA_CHAR, ALPHANUMERIC_CHAR, ADDITIONAL_CHAR, UNICODE_BMP_CHARS
}
機能:
private static final String ALPHA_CHAR = "[a-zA-Z]+";
private static final String ALPHANUMERIC_CHAR = "[a-zA-Z0-9\\_]+";
private static final String ADDITIONAL_CHAR = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~]+";
private static final String UNICODE_BMP_CHARS = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+";
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr,
PatternTypes classAbbrPattern) {
switch (classAbbrPattern) {
case ALPHA_CHAR:
checkUnmatched(classAbbr, ALPHA_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ALPHANUMERIC_CHAR:
checkUnmatched(classAbbr, ALPHANUMERIC_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ADDITIONAL_CHAR:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
case UNICODE_BMP_CHARS:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
}
}
リファクタリング後PatternTypes
、次のように変更されました。
public enum PatternTypes {
/**
* RegEx patterns divided by restriction level
*/
ALPHA_CHAR("[a-zA-Z]+"),
ALPHANUMERIC_CHAR("[a-zA-Z0-9\\_]+"),
ADDITIONAL_CHAR("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~]+"),
UNICODE_BMP_CHARS("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+");
public String getPatternContent() {
return patternContent;
}
private String patternContent;
PatternTypes(String patternContent) {
this.patternContent = patternContent;
}
}
関数は次のように簡略化されます。
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr, PatternTypes classAbbrPattern) {
if (PatternTypes.ADDITIONAL_CHAR.equals(classAbbrPattern) || PatternTypes.UNICODE_BMP_CHARS.equals(classAbbrPattern)){
throw new MalFormedDNException("RegEx pattern:" + classAbbrPattern.name() + "is not allowed for Class Abbr");
}
checkUnmatched(classAbbr, classAbbrPattern.getPatternContent(), CLASS_ABBR_VAR_NAME);
}
何をしたいですか?Switch-Case が十分でないのはなぜですか?
簡単な答えは、if-else を使用することです。
if () {}
else if () {}
...
else if () {}
?
でも、その方が良いとは言えません…
if
(with with else if
and else
) ステートメントはどうですか? switch
整数型または列挙型に対して等価性を使用して切り替えることのみを許可しますが、if
ブール論理を使用できます。
いつでもスイッチをif-else if-else if-else if...
に置き換えることができますが、なぜそうしたいのかわかりません。コンテキストによっては、switch
配列やハッシュマップに置き換えることもできます。